树的题目,往往可以用到三种遍历、以及递归,因为其结构上天然地可以往深处递归,且判断条件也往往不复杂(左右子树都是空的)。

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(草稿)的更多相关文章

  1. 【一天一道LeetCode】#109. Convert Sorted List to Binary Search Tree

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...

  2. 【一天一道LeetCode】#108. Convert Sorted Array to Binary Search Tree

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...

  3. LeetCode算法题-Lowest Common Ancestor of a Binary Search Tree

    这是悦乐书的第197次更新,第203篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第59题(顺位题号是235).给定二叉搜索树(BST),找到BST中两个给定节点的最低共 ...

  4. PAT甲题题解-1043. Is It a Binary Search Tree (25)-二叉搜索树

    博主欢迎转载,但请给出本文链接,我尊重你,你尊重我,谢谢~http://www.cnblogs.com/chenxiwenruo/p/6789220.html特别不喜欢那些随便转载别人的原创文章又不给 ...

  5. [线索二叉树] [LeetCode] 不需要栈或者别的辅助空间,完成二叉树的中序遍历。题:Recover Binary Search Tree,Binary Tree Inorder Traversal

    既上篇关于二叉搜索树的文章后,这篇文章介绍一种针对二叉树的新的中序遍历方式,它的特点是不需要递归或者使用栈,而是纯粹使用循环的方式,完成中序遍历. 线索二叉树介绍 首先我们引入“线索二叉树”的概念: ...

  6. [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 ...

  7. 【一天一道LeetCode】#98. Validate Binary Search Tree

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...

  8. LeetCode第[98]题(Java):Validate Binary Search Tree(验证二叉搜索树)

    题目:验证二叉搜索树 难度:Medium 题目内容: Given a binary tree, determine if it is a valid binary search tree (BST). ...

  9. [数据结构]——二叉树(Binary Tree)、二叉搜索树(Binary Search Tree)及其衍生算法

    二叉树(Binary Tree)是最简单的树形数据结构,然而却十分精妙.其衍生出各种算法,以致于占据了数据结构的半壁江山.STL中大名顶顶的关联容器--集合(set).映射(map)便是使用二叉树实现 ...

随机推荐

  1. 牛客-牛牛的Link Power II

    题目传送门 sol:可以用线段树来维护,线段树的节点除了标配的$l$和$r$同时记录该区间$link$的个数记为$cnt$,该区间$link$点的和记为$sum$,该区间题目中所谓的能量记为$dis$ ...

  2. 大言不惭 swank? talk about sth or speak too confidently

    cán,意思是指说大话而毫不感到难为情.出自<论语·宪问>:“子曰:‘其言之不怍,则为之也难.’”宋·朱熹注:“大言不惭,则无必为之志,而不自度其能否也.欲践其言,其不难哉!” 是不是类似 ...

  3. 缩写: i = i + 1 和 i += 1,可以看做是 i 自加的值 是1。

    i +=  1; 这样有助于记忆: i自加的值等于1

  4. 吴裕雄--天生自然python学习笔记:python 用pygame模块制作 MP3 音乐播放器

    利用 music 对象来制作一个 MP3 音乐播放器 . 应用程序总览 从歌曲清单中选择指定的歌曲,单击“播放”按钮可开始播放, 在播放 xxx 歌曲”的信息. 歌曲播放的过程中,可以暂停.停止,也可 ...

  5. text-overflow属性

    text-overflow属性有两个值, 默认值是clip:当对象内文本溢出时不显示裁切掉. 另一个就是:ellipsis:对象内文本溢出时显示省略标记(...). 使用text-overflow:e ...

  6. django框架进阶-分页-长期维护

    ##################   分页    ####################### 分页, django有自己内置的分页,但是功能不是很强大,所以自己写一个分页, web页面数据非常 ...

  7. transcription-coupled repair|Germ|HK|TS|Mutation|四类变异

    生命组学-可遗传变异 GC content:碱基: GC content不同的植物对应的gene length,可看作上图的转置: 由GC content看出来碱基变异程度,可以找到对应碱基改变,所以 ...

  8. java通过jdbc插入中文到mysql显示乱码(问号或者乱码)

    对于很多初学者来说,中文字符编码不相同的问题,是一个很烦躁的问题!! 因为很多时候,我们并不知道,到底是哪一层出现了问题? 在这里稍微做个总结~也怕自己今后忘了!! 其实也就三层: 1.前端页面 2. ...

  9. Hexo 下 Markdown 的配置与学习

    本篇 更换 Hexo 下的 Markdown 渲染插件 学习 Markdown 基本语法 ✎更换 Markdown 渲染插件 ✎原因 Hexo 内置的默认渲染插件是 hexo-renderer-mar ...

  10. [Python] 使用Python 3 下载麦子学院视频

    本文基于Python 3,下载麦子学院的视频课程. 本项目只是针对某个具体课程的链接,去寻找该课程所有课时的视频链接并进行下载. 整个项目是非常简单的. 主要涉及的Python: 网络相关:reque ...