• 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)

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

    总结

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

  • 相关阅读:
    ajax、json一些整理(2)
    ajax、json一些整理(1)
    C# DllImport的用法
    asp.net 获取当前项目路径
    C# 中关闭当前线程的四种方式 .
    DataGridView自定义RichTextBox列
    C#winform的datagridview设置选中行
    Other Linker flags 添加 -Objc导致包冲突
    nat打洞原理和实现
    成为顶尖自由职业者必备的七个软技能之四:如何成为销售之王(转)
  • 原文地址:https://www.cnblogs.com/Howfars/p/13458725.html
Copyright © 2020-2023  润新知