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

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

LeetCode

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)

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

总结

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

上一篇:IE4横空出世,IE4.5也在发布,还有为苹果的系统做了这件事!


下一篇:nginx启动服务提示98: Address already in use 解决