zoukankan      html  css  js  c++  java
  • LeetCode Notes_#98_验证二叉搜索树

    LeetCode Notes_#98_验证二叉搜索树

    Contents

    题目

    给定一个二叉树,判断其是否是一个有效的二叉搜索树。
    假设一个二叉搜索树具有如下特征:
    节点的左子树只包含小于当前节点的数。
    节点的右子树只包含大于当前节点的数。
    所有左子树和右子树自身必须也是二叉搜索树。
    示例 1:

    输入:
        2
       / 
      1   3
    输出: true

    示例 2:

    输入:
        5
       / 
      1   4
         / 
        3   6
    输出: false
    解释: 输入为: [5,1,4,null,null,3,6]。
         根节点的值为 5 ,但是其右子节点值为 4 。

    思路分析

    整体来说,有两种思路。

    • 前序遍历+上下界: BST当中每个节点都有上下界,如果所有节点的值在上下界规定的范围内,这个树就是二叉搜索树。通过前序遍历的顺序,逐个验证每个节点是否符合BST的要求,就可以判别二叉树是否是BST。
      • 上下界的递推规律(从父节点的上下界递推左右孩子的上下界)
        • 对于左孩子节点,它的上界是父节点的值,下界还是父节点的下界
        • 对于右孩子节点,它的下界是父节点的值,上界还是父节点的上界
    • 中序遍历: BST的中序遍历是递增序列。利用这个特性,我们中序遍历输入的二叉树,每一次都比较当前节点的值和前一个节点的值,如果不满足递增,就不是BST,都满足递增,就是BST。

    具体来说,两种思路都可以写成递归和非递归的形式,都是在二叉树遍历模板的基础上进行修改,加入上述逻辑即可。

    递归

    本题的递归函数比较特殊,跟普通的二叉树遍历递归有点区别。
    递归函数返回值类型是boolean,含义是:当前的子树是否是BST。

    终止条件

    遇到null节点,说明越过了叶子节点。将null也看作一棵子树,null其实也是一个BST,所以返回true,由此开始回溯。

    递推过程

    两种方法的递推过程不同。

    1. 前序遍历 + 上下界
      • 判定当前节点的值是否在上下界范围内,不在则返回false
      • 开启左右子树递归,更新左右子树的上下界,判定左右子树是否是BST,不是则返回false
    2. 中序遍历
      • 开启左子树递归,判定左子树是否是BST,不是则返回false
      • 判定当前节点的值与前一个节点的值是否满足递增,不是则返回false;更新pre
      • 开启右子树递归,判定右子树是否是BST,不是则返回false

    回溯返回值

    • 任何不满足条件的状况,返回false到上一层递归函数,最终结果也一定是false
    • 一直没有返回false,返回true到上一层递归函数

    非递归

    非递归写法是递归写法+非递归遍历模板的组合,只需要简单改动非递归遍历的模板即可,不再详细分析。

    解答

    解答1:前序遍历+上下界+递归

    class Solution {
        public boolean isValidBST(TreeNode root) {
            return recur(root, null, null);
        }
        //因为需要添加上下界参数lower,upper,所以需要重新定义一个递归方法
        //整体来看是前序遍历,需要用两个参数记录上下界
        private boolean recur(TreeNode node, Integer lower, Integer upper){
            //null表示传入的子树是空树,空树也是二叉搜索树,所以返回true
            if(node == null) return true;
            int val = node.val;
            if(lower != null && val <= lower) return false;
            if(upper != null && val >= upper) return false;
            //node的右子树node.right的下界修改为val,上界保持不变
            if(!recur(node.right,val,upper)) return false;
            //node的左子树node.left的上界修改为val,下界保持不变
            if(!recur(node.left,lower,val)) return false;
            return true;
        }
    }

    解答2:前序遍历+上下界+非递归

    class Solution {
        //栈数据结构不一定非要用Stack类实现,也可以用LinkedList类
        //初始化三个栈
        LinkedList<TreeNode> stack = new LinkedList();//保存树节点
        LinkedList<Integer> uppers = new LinkedList();//保存树节点对应的上界
        LinkedList<Integer> lowers = new LinkedList();//保存树节点对应的下界
        
        public boolean isValidBST(TreeNode root) {
            if(root == null) return true;
            Integer lower = null, upper = null;
            //根节点入栈,upper,lower初始化为null
            update(root, upper, lower);
            while(!stack.isEmpty()){
                //弹出一个节点,并弹出相应的上下界的值,用于判断当前节点是否符合BST定义
                TreeNode node = stack.poll();
                lower = lowers.poll();
                upper = uppers.poll();
                if(lower != null && node.val <= lower) return false;
                if(upper != null && node.val >= upper) return false;
                //压入右子树根节点,以及对应的上下界的值,待判断
                //右子树根节点的下界修改为node.val
                if(node.right != null) update(node.right, node.val, upper);
                //压入左子树根节点,以及对应的上下界的值,待判断
                //左子树根节点的上界修改为node.val
                if(node.left != null) update(node.left, lower, node.val);
            }
            return true;
        }
    
        //同时向三个栈压入数据:当前访问到的树节点,当前节点对应的上界和下界
        private void update(TreeNode root, Integer lower, Integer upper){
            stack.add(root);
            lowers.add(lower);
            uppers.add(upper);
        }
    }

    解答3:中序遍历+递归

    class Solution {
        long pre = Long.MIN_VALUE;
        public boolean isValidBST(TreeNode root) {
            //越过叶子节点,访问到null,null也是BST,返回true,开始回溯
            if(root == null) return true;
            if(!isValidBST(root.left)) return false;
            if(root.val <= pre) return false;
            pre = root.val;
            if(!isValidBST(root.right)) return false;
            //回溯到根节点,还是没有返回false,就返回true
            return true;  
        }
    }

    这里的递归函数返回值是boolean,所以可以将其写到if语句当中。

    解答4:中序遍历+非递归

    class Solution {
        public boolean isValidBST(TreeNode root) {
            Stack<TreeNode> stack = new Stack();
            long pre = Long.MIN_VALUE;
            //只有最左边的一排节点入栈,右侧的节点根本没有入栈,所以不能只根据!stack.isEmpty()判断是否终止循环
            while(!stack.isEmpty() || root != null){
                //root代表一棵子树,首先要遍历到这棵子树的左下角节点,作为遍历的第一个节点
                while(root != null){
                    stack.push(root);
                    root = root.left;
                }
                //如果root不是null,执行上述循环,弹出左下角节点
                //如果root是null,不执行上述循环,弹出上一层的最左侧节点,即回溯到上一层
                root = stack.pop();
                //对出栈节点的操作:与pre做比较,然后更新pre
                if(root.val <= pre) return false;
                pre = root.val;
                //最后访问右节点
                root = root.right;
            }
            return true;
        }
    }

    参考非递归中序遍历的模板,其实仅仅是对于出栈后的操作进行了简单修改。

    class Solution {
        public List<Integer> inOrderTraversal(TreeNode root) {
            if (root == null) {
                return new LinkedList<>();
            }
            List<Integer> res = new LinkedList<>();
            Deque<TreeNode> stack = new LinkedList<>();
            TreeNode node = root;
            while(node != null || !stack.isEmpty()) {
                while (node != null) {
                    stack.addLast(node);
                    node = node.left;
                }
                //出栈
                node = stack.removeLast();
                //对出栈节点的操作:加入res列表
                res.add(node.val);
                node = node.right;
            }
            return res;
        }
    }

    复杂度分析

    4种解法复杂度是一样的。
    时间复杂度:O(n),都要遍历所有节点,所以是O(n)
    空间复杂度:O(n)

    • 递归:递归调用产生调用栈
    • 非递归:使用辅助栈保存节点数据

    总结

    树的题目大多都跟树的遍历相关,需要掌握二叉树遍历模板,对于每一道树的题,都可以用模板来套。题目做完后,有意识的跟模板进行比较,建立联系。将来遇到类似题,可以举一反三。

  • 相关阅读:
    C/C++多文件之间的变量定义
    PKU POJ 2186 Popular Cows 强连通分量
    重载函数
    ZOJ 2763 Prison Break
    201357 训练赛总结
    hdu 4467 Graph 构造
    201356 训练赛总结
    201353 NEERC 2012, Eastern subregional contest
    2013512 CF 183 总结
    一道动态规划
  • 原文地址:https://www.cnblogs.com/Howfars/p/13458725.html
Copyright © 2011-2022 走看看