一道二叉树题的n步优化——LeetCode98validate binary search tree(草稿)
树的题目,往往可以用到三种遍历、以及递归,因为其结构上天然地可以往深处递归,且判断条件也往往不复杂(左右子树都是空的)。
LeetCode 98题讲的是,判断一棵树是不是二叉搜索树。

题目中给的是标准定义:即一个二叉搜索树(binary search tree,简称BST)的每个节点都满足:1.其左侧的整个子树的值都比它小;2.其右侧的整个子树的值都比他大;3.它的左右子树依旧保持二叉搜索树的结构。
第0步
一开始理解错了条件,容易理解成,每个节点只需要比它的左子树大,比它的右子树小,于是得到了这样的递归代码(关于递归,可参加本人第一篇博文:https://www.cnblogs.com/mingyu-li/p/12161704.html):
class Solution {
public boolean isValidBST(TreeNode root) {
// it is a typical in-order traverse
// exit: if null, return true
if(root == null) return true; //结束递归条件
boolean leftValid = true;
boolean rightValid = true;
if(root.left != null && root.left.val >= root.val) leftValid = true; //检查左子节点
if(root.right != null && root.right.val <= root.val) rightValid = true; //检查右子节点
return leftValid && rightValid && isValidBST(root.left) && isValidBST(root.right);
}
}
问题是很明显的,实际上1、2两个条件未能满足,如果一个节点的左子节点的右子节点又大于这个节点,不违反上述的判断,但是又不符合定义:“整个”左子树全部小于节点。
第1步:正确的左右子树递归
既然问题就出在没有合理进行判断,那么对于一个节点,就深入递归判断其全部的子树就是,但是平均来看,一个节点都需要再遍历与总结点数成正比的节点来判断大小了。
class Solution {
public boolean isValidBST(TreeNode root) {
// exit: if null, return true
if(root == null) return true;
// the recursion of recursion: the root should be larger than any element in left
// the root should be smaller than any element in right
if(!checkLeft(root.left, root.val) || !checkRight(root.right, root.val)) return false;
return isValidBST(root.left) && isValidBST(root.right);
}
public boolean checkLeft(TreeNode node, int value){
if(node == null) return true;
if(node.val >= value) return false;
return checkLeft(node.left, value) && checkLeft(node.right, value);
}
public boolean checkRight(TreeNode node, int value){
if(node == null) return true;
if(node.val <= value) return false;
return checkRight(node.right, value) && checkRight(node.left, value);
}
}
这样,时间复杂度大约是O(n^2),空间复杂度是O(n)。
第2.1步
一个BST其实有一个性质,就是其中序遍历的结果应该是有序的,此处是升序,也就是递增的。很自然且容易写的办法就是用一个全局变量链表把节点的值先保存进去,再一一进行比较是否是升序。
从这步开始,时间复杂度就已经一步优化到了O(n)了,因为其合理利用了BST的中序遍历的性质,如果想不到,也就很难做这样的优化了。不过,在空间上,虽然同样是O(n),这步却略高于第一步,因为还新开辟了一个ArrayList(虽然递归的栈也是主要来源)。
class Solution {
ArrayList<Integer> inorderset = new ArrayList();
public boolean isValidBST(TreeNode root) {
if(root == null) return true;
inorderTraverse(root);
for(int i = 1; i < inorderset.size(); i++){
if(inorderset.get(i) <= inorderset.get(i - 1)) return false;
}
return true;
}
public void inorderTraverse(TreeNode root){
if(root == null) return;
inorderTraverse(root.left);
inorderset.add(root.val);
inorderTraverse(root.right);
}
}
第2.2步
上述方法较大的问题就是太过暴力,先把全部数据遍历保存了才比较,虽然易于理解与实现,但是对这个题目的要求有点舍近求远了。所以比较合理的想法就是依照官方题解,确认每个点的上下界。
左节点的范围:(根/父节点的左边界,父节点的值);右节点的范围:(父节点的值,父节点的右边界)
细节:最大最小值的表示方法,一种是利用Integer.MAX_VALUE,若输入是长整形long,则只能考虑使用null以及java的自动拆包装包特性了。
因为多做了以上的数学分析,代码显得更加简洁,但是不那么intutive了,时间复杂度O(n),空间复杂度O(n)。这一方法也就是树的DFS,每一轮向树的左右孩子移动,然后找不到时结束。(从结果来看,时间上的最优解似乎出自这个方法)
class Solution {
public boolean isValidBST(TreeNode root) {
boolean res = helper(root, null, null);
return res;
}
public boolean helper(TreeNode node, Integer min, Integer max){
if(node == null) return true;
if(min != null && min >= node.val) return false;
if(max != null && max <= node.val) return false;
return helper(node.left, min, node.val) && helper(node.right, node.val, max);
}
}
第2.3步
对于上述遍历方法,也可以考虑使用栈/队列来实现,但是如果使用栈,按深度优先方法,保存的变量则太多,不如使用BFS,遍历一层只保存上层的必要信息。实现BFS的必要操作是使用queue。
空间、时间都是O(n),但是时间上略慢。
class Solution {
public boolean isValidBST(TreeNode root) {
if(root == null) return true;
Queue<TreeNode> queue = new LinkedList();
Queue<Integer> maxList = new LinkedList();
Queue<Integer> minList = new LinkedList();
queue.add(root);
maxList.add(null);
minList.add(null);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
Integer max = maxList.poll();
Integer min = minList.poll();
if(max != null && max <= node.val) return false;
if(min != null && min >= node.val) return false;
if(node.left != null){
queue.add(node.left);
maxList.add(node.val);
minList.add(min);
}
if(node.right != null){
queue.add(node.right);
maxList.add(max);
minList.add(node.val);
}
}
return true;
}
}
第2.4步
e. 使用DFS+stack实现:(参考:https://leetcode.wang/leetCode-98-Validate-Binary-Search-Tree.html)
public boolean isValidBST(TreeNode root) {
if (root == null || root.left == null && root.right == null) {
return true;
}
//利用三个栈来保存对应的节点和区间
LinkedList<TreeNode> stack = new LinkedList<>();
LinkedList<Integer> minValues = new LinkedList<>();
LinkedList<Integer> maxValues = new LinkedList<>();
//头结点入栈
TreeNode pNode = root;
stack.push(pNode);
minValues.push(null);
maxValues.push(null);
while (pNode != null || !stack.isEmpty()) {
if (pNode != null) {
//判断栈顶元素是否符合
Integer minValue = minValues.peek();
Integer maxValue = maxValues.peek();
TreeNode node = stack.peek();
if (minValue != null && node.val <= minValue) {
return false;
}
if (maxValue != null && node.val >= maxValue) {
return false;
}
//将左孩子加入到栈
if(pNode.left!=null){
stack.push(pNode.left);
minValues.push(minValue);
maxValues.push(pNode.val);
}
pNode = pNode.left;
} else { // pNode == null && !stack.isEmpty()
//出栈,将右孩子加入栈中
TreeNode node = stack.pop();
minValues.pop();
Integer maxValue = maxValues.pop();
if(node.right!=null){
stack.push(node.right);
minValues.push(node.val);
maxValues.push(maxValue);
}
pNode = node.right;
}
}
return true;
}
第2.5步
既然b中已经考虑了中序遍历,但是实际上中序遍历又不需要专门保存往一个数组中,因为题目并不需要这样,多浪费了空间,因此可以考虑使用iteration的方式实现中序遍历,用一个stack来做。
此时,时间复杂度是O(n),空间复杂度降低到了O(h),当二叉树平衡时,实际上最好可以达到O(logn),因此在LeetCode上结果也达到了80.93%。
这一思路的本质是利用二叉树的中序遍历的iteration的化归,如果能看出这一点,实际上不难联想到这里了。(到这步,也已几近于空间上的最优了,当然,最好还是希望树型接近平衡二叉树)
class Solution {
public boolean isValidBST(TreeNode root) {
// this is using in-order traverse iteration version
if(root == null) return true;
Stack<TreeNode> stack = new Stack();
TreeNode pre = null; // denote the node before current node
while(root != null || !stack.isEmpty()){
while(root != null){
// push all left nodes in the stack
stack.push(root);
root = root.left;
}
root = stack.pop();
if(pre != null && pre.val >= root.val) return false;
pre = root;
root = root.right;
}
return true;
}
}
第3步
由于在中序遍历中,还有一个Morris算法,可以再降低中序遍历到O(1),如果使用这一结构解决这个问题,可以达到空间最优。(算法之后补上)
一道二叉树题的n步优化——LeetCode98validate binary search tree(草稿)的更多相关文章
- 【一天一道LeetCode】#109. Convert Sorted List to Binary Search Tree
一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...
- 【一天一道LeetCode】#108. Convert Sorted Array to Binary Search Tree
一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...
- LeetCode算法题-Lowest Common Ancestor of a Binary Search Tree
这是悦乐书的第197次更新,第203篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第59题(顺位题号是235).给定二叉搜索树(BST),找到BST中两个给定节点的最低共 ...
- PAT甲题题解-1043. Is It a Binary Search Tree (25)-二叉搜索树
博主欢迎转载,但请给出本文链接,我尊重你,你尊重我,谢谢~http://www.cnblogs.com/chenxiwenruo/p/6789220.html特别不喜欢那些随便转载别人的原创文章又不给 ...
- [线索二叉树] [LeetCode] 不需要栈或者别的辅助空间,完成二叉树的中序遍历。题:Recover Binary Search Tree,Binary Tree Inorder Traversal
既上篇关于二叉搜索树的文章后,这篇文章介绍一种针对二叉树的新的中序遍历方式,它的特点是不需要递归或者使用栈,而是纯粹使用循环的方式,完成中序遍历. 线索二叉树介绍 首先我们引入“线索二叉树”的概念: ...
- [CareerCup] 4.7 Lowest Common Ancestor of a Binary Search Tree 二叉树的最小共同父节点
4.7 Design an algorithm and write code to find the first common ancestor of two nodes in a binary tr ...
- 【一天一道LeetCode】#98. Validate Binary Search Tree
一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...
- LeetCode第[98]题(Java):Validate Binary Search Tree(验证二叉搜索树)
题目:验证二叉搜索树 难度:Medium 题目内容: Given a binary tree, determine if it is a valid binary search tree (BST). ...
- [数据结构]——二叉树(Binary Tree)、二叉搜索树(Binary Search Tree)及其衍生算法
二叉树(Binary Tree)是最简单的树形数据结构,然而却十分精妙.其衍生出各种算法,以致于占据了数据结构的半壁江山.STL中大名顶顶的关联容器--集合(set).映射(map)便是使用二叉树实现 ...
随机推荐
- sbt 设置
修改 sbtopts for shell # zkk -sbt-dir D:/DATA/.sbt -sbt-boot D:/DATA/.sbt/boot -ivy D:/DATA/.ivy2 修改 s ...
- 吴裕雄--天生自然 PYTHON3开发学习:数据库连接 - PyMySQL 驱动
import pymysql # 打开数据库连接 db = pymysql.connect("localhost","testuser","test1 ...
- F - No Link, Cut Tree! Gym - 101484F
Marge is already preparing for Christmas and bought a beautiful tree, decorated with shiny ornaments ...
- 关于TensorFlow2的tf.function()和AutoGraph的一些问题解决
在使用TensorFlow的AutoGraph的时候出现了一些问题,特此记录 AutoGraph did not convert this function. Try decorating it di ...
- 微信中的APP、公众号、小程序的openid及unionid介绍
微信中的APP.公众号.小程序的openid及unionid介绍 1.unionid 如果开发者拥有多个移动应用.网站应用.和公众帐号(包括小程序),可通过 UnionID 来区分用户的唯一性,因为只 ...
- android适配全机型悬浮框、视频APP项目、手势操作、Kotlin妹子App、相机图片处理等源码
Android精选源码 图片滤镜处理,相机滤镜实时处理,相机拍照录制 android仿爱壁纸App更换壁纸效果源码 基于Kotlin+MVP+Retrofit+RxJava+Glide 等架构实现短视 ...
- operator和if结构
1.比较运算符:>,<,==,!=,>=,<= 注意:所有比较运算符的结果都是布尔值举例: 123456789 print(100>10) print(100<10 ...
- [LC] 167. Two Sum II - Input array is sorted
Given an array of integers that is already sorted in ascending order, find two numbers such that the ...
- Python opencv PIL numpy base64互相转化
PIL2numpy and numpy2PIL from PIL import Image import numpy image = Image.open('timg.jpeg')# image is ...
- Tarjan模板——求强连通分量
Tarjan求强连通分量的流程在这个博客讲的很清楚,再加上我也没理解透,这里就不写了. 缩点:将同一个连通块内的点视为同一个点. 扔一道模板题:codeVS2822爱在心中 第一问很显然就是求点数大于 ...