一道二叉树题的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)便是使用二叉树实现 ...
随机推荐
- vncserver
## install packages yum install tigervnc-server xhost ## start vnc and xhost vncserver export DISPLA ...
- Vue框架:挂载点-过滤器-事件指令-表单指令
近期学习安排 1.Vue框架 前台html+css+js框架,是不同于js与JQuery的数据驱动框架, 学习的知识点:指令 | 实例成员 | vue项目 2.drf框架 django的插件,完成前 ...
- Table布局的优缺点
总结 Table布局的缺点是比其它html标记占更多的字节,会阻挡浏览器渲染引擎的渲染顺序,会影响其内部的某些布局属性的生效,优点就是用table做表格是完全正确的 Tables的缺点 1.Table ...
- MySQL数据库中索引的数据结构是什么?(B树和B+树的区别)
B树(又叫平衡多路查找树) 注意B-树就是B树,-只是一个符号. B树的性质(一颗M阶B树的特性如下) 1.定义任意非叶子结点最多只有M个儿子,且M>2: 2.根结点的儿子数为[2, M]: 3 ...
- Nearby Bicycles
With fast developments of information and communication technology, many cities today have establish ...
- iOS漂亮的Toolbar动画、仿美团主页、简易笔记本、流失布局、标签分组等源码
iOS精选源码 JPLiquidLayout 简单易用的流式布局 labelGroupAndStreamSwift---标签分组,单选,多选 iOS采用UITableView和UIScrollView ...
- 奇点云COO刘莹应邀出席《APEC SME大数据与人工智能论坛》
10月24日-25日,由亚太经合组织(APEC).韩国中小型及初创企业管理局(the Ministry of SMEs & Startups of Korea)主办的「APEC SME 大数据 ...
- 59)PHP,管理员表中所存在的项
用户ID 用户名 用户密码 用户权限(就是他的角色等级,比如是1级 2级, 三级等等) 上次登录的IP 上次登录的时间
- python语法基础-函数-基础-长期维护
############### 函数的定义调用,返回值和返回值接收 ############## def mylen(): s = "myname" i = 0 for ...
- 双因子方差分析|adjusted R|强度|SSA|SSE|SST|
应用统计学 方差分析的基本假设: 组间组平均与总平均的不同是由treatment引发的.单个值与组平均的不同是由组内error引发的. 如果没有处理误差SSA=SSE,所以右尾假设如果F>1则处 ...