力扣解题-98. 验证二叉搜索树

0 阅读7分钟

力扣解题-98. 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 严格小于 当前节点的数。
  • 节点的右子树只包含 严格大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

image.png

输入:root = [2,1,3]

输出:true

示例 2:

image.png

输入:root = [5,1,4,null,null,3,6]

输出:false

解释:根节点的值是 5 ,但是右子节点的值是 4 。

提示:

树中节点数目范围在[1, 10⁴] 内

-2³¹ <= Node.val <= 2³¹ - 1

Related Topics

树、深度优先搜索、广度优先搜索、二叉搜索树、二叉树


第一次解答

解题思路

核心方法:BST中序遍历验证法,利用BST“中序遍历结果为严格升序序列”的核心特性,通过中序遍历过程中比较当前节点与前一个节点的值,判断是否满足“严格升序”,从而验证是否为有效BST,时间复杂度O(n)、空间复杂度O(h)(h为树的高度),是本题最直观且高效的解法。

核心逻辑拆解

验证BST的核心规律:

  1. BST关键特性:一个有效的BST,其中序遍历结果必然是严格升序的序列(如示例1中序遍历[1,2,3]是严格升序,示例2中序遍历[1,5,3,4,6]不是严格升序);
  2. 验证逻辑:只需在中序遍历过程中,检查每个节点值是否严格大于前一个节点值,若出现“当前节点值 ≤ 前一个节点值”的情况,即可判定不是有效BST;
  3. 算法核心:通过中序遍历遍历整棵树,维护前一个节点值prev,实时对比当前节点与prev的大小,一旦发现不满足严格升序则标记结果为false
具体执行逻辑
  1. 初始化变量
    • prev:记录中序遍历的前一个节点值,初始为null(无前置节点);
    • result:标记是否为有效BST,初始为true(默认有效,发现违规则置为false);
  2. 中序遍历递归
    • 递归终止条件:当前节点node == null,直接返回;
    • 先递归遍历左子树(中序遍历“左”,优先验证左子树的有效性);
    • 核心验证:若prev != nullnode.val <= prev,说明不满足严格升序,将result置为false
    • 更新prev为当前节点值(为下一个节点的比较做准备);
    • 递归遍历右子树(中序遍历“右”,验证右子树的有效性);
  3. 返回结果:遍历完成后,result即为最终验证结果。
执行流程可视化
示例中序遍历序列关键对比点result结果验证结论
示例1[1,2,3]2>1、3>2true有效BST
示例2[1,5,3,4,6]3≤5(违规)false无效BST
关键细节说明
  • 严格升序要求:必须满足node.val > prev,而非node.val >= prev(题目明确要求“严格小于/大于”);
  • 提前终止优化(可选):代码中未做提前终止,即使发现result=false仍会遍历完所有节点,若需优化可在递归开头增加if (!result) return;,减少无效遍历;
  • 数据类型边界prev使用Integer类型,可兼容节点值为Integer.MIN_VALUE的场景(若用long可进一步避免边界问题,但当前逻辑已满足题目要求);
  • 边界处理:题目保证节点数≥1,无需处理空树场景。
性能说明
  • 时间复杂度:O(n)(每个节点仅被访问一次,中序遍历的线性复杂度);
  • 空间复杂度:O(h)(递归栈深度等于树的高度,平衡BST为O(logn),斜树为O(n));
  • 优势:
    1. 利用BST核心特性,逻辑直观,新手易理解;
    2. 代码简洁,仅需基础的中序遍历框架+值对比;
    3. 无额外空间开销(无需存储完整遍历序列),空间效率最优。
    private Integer prev = null;          // 记录中序遍历的前一个节点值
    private boolean result=true;
    public boolean isValidBST(TreeNode root) {
        inorder(root);
        return result;
    }
    public void inorder(TreeNode node) {
        if (node == null) {
            return;
        }
        inorder(node.left);
        if (prev != null) {
           if(node.val<=prev){
               result=false;
           }
        }
        prev=node.val;
        inorder(node.right);
    }

示例解答

解题思路

解法1:递归范围验证法(更贴合BST定义)

核心方法:直接基于BST的定义验证,给每个节点设定合法的取值范围(左边界low、右边界high),递归验证每个节点值是否在(low, high)范围内,左子树的右边界为当前节点值,右子树的左边界为当前节点值,时间复杂度O(n)、空间复杂度O(h)。

代码实现
public boolean isValidBST(TreeNode root) {
    // 初始范围:Long.MIN_VALUE ~ Long.MAX_VALUE,兼容int边界值
    return validate(root, Long.MIN_VALUE, Long.MAX_VALUE);
}

private boolean validate(TreeNode node, long low, long high) {
    if (node == null) {
        // 空树是有效BST
        return true;
    }
    // 当前节点值超出范围,直接返回false
    if (node.val <= low || node.val >= high) {
        return false;
    }
    // 递归验证左子树(左子树的上界为当前节点值)和右子树(右子树的下界为当前节点值)
    return validate(node.left, low, node.val) && validate(node.right, node.val, high);
}
核心逻辑说明
  1. 范围设定
    • 根节点的合法范围是(-∞, +∞),用Long.MIN_VALUELong.MAX_VALUE避免int边界溢出(如节点值为2³¹-1时,后续比较不会越界);
    • 左子节点的合法范围是(父节点的low, 父节点值),右子节点的合法范围是(父节点值, 父节点的high)
  2. 递归验证
    • 若当前节点值≤low或≥high,说明违反BST规则;
    • 只有左子树和右子树都验证通过,当前节点所在子树才是有效BST。
性能说明
  • 时间复杂度:O(n)(每个节点仅被访问一次);
  • 空间复杂度:O(h)(递归栈深度等于树的高度);
  • 优势:
    1. 直接贴合BST的定义,逻辑更严谨;
    2. 避免使用类成员变量,代码封装性更好,无线程安全问题;
    3. 天然支持提前终止(一旦某子树验证失败,立即返回false);
  • 劣势:需理解“范围传递”的递归逻辑,新手入门门槛略高。
解法2:中序遍历迭代法(非递归,避免栈溢出)

核心方法:使用栈模拟中序遍历的递归过程,同样通过对比当前节点与前一个节点的值验证严格升序,避免递归栈溢出风险(如斜树场景)。

代码实现
public boolean isValidBST(TreeNode root) {
    Stack<TreeNode> stack = new Stack<>();
    TreeNode curr = root;
    Long prev = null; // 用Long避免Integer.MIN_VALUE的边界问题
    
    while (curr != null || !stack.isEmpty()) {
        // 遍历到左子树最深处
        while (curr != null) {
            stack.push(curr);
            curr = curr.left;
        }
        // 弹出并处理当前节点
        curr = stack.pop();
        // 核心验证:严格升序检查
        if (prev != null && curr.val <= prev) {
            return false;
        }
        prev = (long) curr.val;
        // 遍历右子树
        curr = curr.right;
    }
    return true;
}
核心逻辑说明
  1. 栈模拟递归:通过栈存储待处理节点,先遍历左子树最深处,再弹出节点验证,最后遍历右子树;
  2. 边界优化prev使用Long类型,避免节点值为Integer.MIN_VALUE时的误判(如根节点为Integer.MIN_VALUEprev初始为null不会触发验证);
  3. 提前终止:一旦发现不满足严格升序,立即返回false,无需遍历剩余节点。
性能说明
  • 时间复杂度:O(n)(最优O(h),发现违规立即终止;最坏O(n),遍历所有节点);
  • 空间复杂度:O(h)(栈深度等于树的高度);
  • 优势:
    1. 非递归实现,避免极端斜树导致的栈溢出;
    2. 提前终止遍历,效率更高;
    3. 解决了Integer.MIN_VALUE的边界问题;
  • 劣势:代码量略多于递归法,需记忆中序遍历迭代模板。

总结

  1. 中序遍历递归验证法(第一次解答):O(n)时间+O(h)空间,逻辑直观、代码简洁,是新手入门的首选解法;
  2. 递归范围验证法:O(n)时间+O(h)空间,贴合BST定义、封装性好,工程实践更推荐;
  3. 中序遍历迭代法:O(n)时间+O(h)空间,避免栈溢出、支持提前终止,适配高树高场景;
  4. 关键技巧
    • 核心思想:验证BST的核心是“严格升序”(中序遍历法)或“节点值在合法范围”(范围验证法);
    • 边界处理:使用Long类型存储前一个节点值,避免Integer.MIN_VALUE的误判;
    • 效率优化:递归时增加提前终止逻辑,减少无效遍历。