数据结构与算法之深入解析“验证二叉搜索树”的求解思路与算法示例

Posted Serendipity·y

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法之深入解析“验证二叉搜索树”的求解思路与算法示例相关的知识,希望对你有一定的参考价值。

一、题目要求

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

输入:root = [2,1,3]
输出:true
  • 示例 2:

输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4
  • 提示:
    • 树中节点数目范围在[1, 104] 内;
    • -231 <= Node.val <= 231 - 1。

二、求解算法

① 中序遍历

  • 中序遍历时,判断当前节点是否大于中序遍历的前一个节点,如果大于,说明满足 BST,继续遍历;否则直接返回 false。
  • Java 示例:
class Solution 
    long pre = Long.MIN_VALUE;
    public boolean isValidBST(TreeNode root) 
        if (root == null) 
            return true;
        
        // 访问左子树
        if (!isValidBST(root.left)) 
            return false;
        
        // 访问当前节点:如果当前节点小于等于中序遍历的前一个节点,说明不满足BST,返回 false;否则继续遍历。
        if (root.val <= pre) 
            return false;
        
        pre = root.val;
        // 访问右子树
        return isValidBST(root.right);
    

② 递归

  • 二叉搜索树的性质就是左子树中所有节点的值都小于(这里是<而非<=)根节点,右子树中所有节点的值都大于(同理这里是>而非>=)根节点;
  • 直接递归,验证左子树的时候,将左子树值的最小范围和最大范围作为参数传入,同理右子树也是如此;
  • 你可能会有疑问:BST 不是左小右大么,那只要检查 root.val > root.left.val 且 root.val < root.right.val 不就行了?这样是不对的,因为 BST 左小右大的特性是指 root.val 要比左子树的所有节点都更大,要比右子树的所有节点都小,只检查左右两个子节点当然是不够的。
  • Java 示例:
// 限定以 root 为根的子树节点必须满足 max.val > root.val > min.val
var isValidBST = function (root, min = -Infinity, max = Infinity) 
  // 如果是空节点
  if (!root) return true;
  // 若 root.val 不符合 max 和 min 的限制,说明不是合法 BST(所以判断条件是<=而非<,同样是>=而非>)
  if (root.val <= min || root.val >= max) return false;
  // 限定左子树的最大值是 root.val,右子树的最小值是 root.val
  return (
    isValidBST(root.left, min, root.val) &&
    isValidBST(root.right, root.val, max)
  );
;
  • 可以继续简化:
// 限定以 root 为根的子树节点必须满足 max.val > root.val > min.val
var isValidBST = function (root, min = -Infinity, max = Infinity) 
  // 如果是空节点
  if (!root) return true;
  // 当前节点的值大于最小值,小于最大值;(换句话说,当前节点的值大于左子树所有节点的值,小于右子树中所有节点的值 )
  // 限定左子树的最大值是 root.val,右子树的最小值是 root.val
  return (
    root.val > min &&
    root.val < max &&
    isValidBST(root.left, min, root.val) &&
    isValidBST(root.right, root.val, max)
  );
;

③ LeetCode 官方解法

  • 递归:
    • 要解决这道题首先要了解二叉搜索树有什么性质可以利用,由题目给出的信息可以知道:如果该二叉树的左子树不为空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;它的左右子树也为二叉搜索树。
    • 这启示我们设计一个递归函数 helper(root, lower, upper) 来递归判断,函数表示考虑以 root 为根的子树,判断子树中所有节点的值是否都在 (l,r) 的范围内(注意是开区间)。如果 root 节点的值 val 不在 (l,r) 的范围内说明不满足条件直接返回,否则要继续递归调用检查它的左右子树是否满足,如果都满足才说明这是一棵二叉搜索树。
    • 那么根据二叉搜索树的性质,在递归调用左子树时,需要把上界 upper 改为 root.val,即调用 helper(root.left, lower, root.val),因为左子树里所有节点的值均小于它的根节点的值。同理递归调用右子树时,需要把下界 lower 改为 root.val,即调用 helper(root.right, root.val, upper)。
    • 函数递归调用的入口为 helper(root, -inf, +inf), inf 表示一个无穷大的值。
    • 以示例 2 为例:




    • C++ 示例:
class Solution 
public:
    bool helper(TreeNode* root, long long lower, long long upper) 
        if (root == nullptr) 
            return true;
        
        if (root -> val <= lower || root -> val >= upper) 
            return false;
        
        return helper(root -> left, lower, root -> val) && helper(root -> right, root -> val, upper);
    
    bool isValidBST(TreeNode* root) 
        return helper(root, LONG_MIN, LONG_MAX);
    
;
    • Java 示例:
class Solution 
    public boolean isValidBST(TreeNode root) 
        return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
    

    public boolean isValidBST(TreeNode node, long lower, long upper) 
        if (node == null) 
            return true;
        
        if (node.val <= lower || node.val >= upper) 
            return false;
        
        return isValidBST(node.left, lower, node.val) && isValidBST(node.right, node.val, upper);
    

  • 中序遍历:
    • 基于上文中提及的性质,可以进一步知道二叉搜索树「中序遍历」得到的值构成的序列一定是升序的,这启示我们在中序遍历的时候实时检查当前节点的值是否大于前一个中序遍历到的节点的值即可。如果均大于说明这个序列是升序的,整棵树是二叉搜索树,否则不是,可以使用栈来模拟中序遍历的过程。
    • 如果不知道中序遍历是什么,可以简单提及一下,中序遍历是二叉树的一种遍历方式,它先遍历左子树,再遍历根节点,最后遍历右子树。而二叉搜索树保证了左子树的节点的值均小于根节点的值,根节点的值均小于右子树的值,因此中序遍历以后得到的序列一定是升序序列。
    • C++ 示例:
class Solution 
public:
    bool isValidBST(TreeNode* root) 
        stack<TreeNode*> stack;
        long long inorder = (long long)INT_MIN - 1;

        while (!stack.empty() || root != nullptr) 
            while (root != nullptr) 
                stack.push(root);
                root = root -> left;
            
            root = stack.top();
            stack.pop();
            // 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
            if (root -> val <= inorder) 
                return false;
            
            inorder = root -> val;
            root = root -> right;
        
        return true;
    
;
    • Java 示例:
class Solution 
    public boolean isValidBST(TreeNode root) 
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        double inorder = -Double.MAX_VALUE;

        while (!stack.isEmpty() || root != null) 
            while (root != null) 
                stack.push(root);
                root = root.left;
            
            root = stack.pop();
              // 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
            if (root.val <= inorder) 
                return false;
            
            inorder = root.val;
            root = root.right;
        
        return true;
    

以上是关于数据结构与算法之深入解析“验证二叉搜索树”的求解思路与算法示例的主要内容,如果未能解决你的问题,请参考以下文章

数据结构与算法之深入解析“股票的最大利润”的求解思路与算法示例

数据结构与算法之深入解析“安装栅栏”的求解思路与算法示例

数据结构与算法之深入解析“最长连续序列”的求解思路与算法示例

数据结构与算法之深入解析“路径总和”的求解思路与算法示例

数据结构与算法之深入解析“斐波那契数”的求解思路与算法示例

数据结构与算法之深入解析“股票价格跨度”的求解思路与算法示例