一道二叉树题的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)便是使用二叉树实现 ...
随机推荐
- istio介绍
核心架构 解决的问题 故障排查 1. 这个请求在哪里失败了?A有调用B吗? 2. 为什么用户的请求/页面hung住了? 3. 为什么系统这么慢?那个组件最慢? 应用容错性 1. 客户端没有配置 ...
- Snapchat欲联手亚马逊推扫一扫功能,社交应用营收来源将有大变化?
当下的社交应用,已经不能完全仅用"社交"的标签进行定义.因为目前的社交应用不仅承载着大众的喜怒哀乐和沟通指责,更在逐渐打造起一个连接多方的生态系统.甚至只从自身的营收.利润出发,社 ...
- python学习——tuple
tuple 上次谈到了列表,而这次所谈的元组其实和列表有许多相似的地方,故元组又叫"戴上了枷锁的列表".这是因为元组不能改动内部的元素,所以就不能使用上次谈到的append.ext ...
- 对象数组和for循环遍历输出学生的信息
public class Student { private int no; private String name; private int age; public Student(int no, ...
- PHP调试工具PHP DEBUG TOOLS 使用方法
一.安装篇安装前的准备环境:必须得先装X-Debug,至于怎样安装X-Debug请看http://www.xdebug.org/docs/install 1. 从http://www.xdebug.o ...
- 微弱信号二次谐波检测的FPGA的实现-总结
首先还是把握大的系统框架: 我要实现的部分不包括DA以及AD的转换,主要是将SSP接收到的数据送入到FIFO中,然后经过FIR带通滤波器的处理后对该信号计算幅值并做PSD,然后处理的信号经过积分够一方 ...
- 获取指定网卡对应的IP地址
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <sys/ty ...
- yield解析
1.yield可以用来为一个函数返回值塞数据 代码: def addlist(alist): for i in alist: alist = [, , , ] for x in addlist(ali ...
- Invalid action class configuration that references an unknown class问题原因之s:select
早先做个练习项目就出现了这个错误,各种查资料,然后各种尝试,依然没有解决,不过可以确定是前台页面导致的. 今天又碰到了这个问题,头疼啊!不能再略过了,使用最笨的方法,一个模块一个模块的排除.先看下我的 ...
- Promise的用法(js&java)
Promise(onsuccess , on fail) f1.then(f2.then(), f3.then()) java CompletableFuture.thenAccept