LeetCode 98:验证二叉搜索树 —— 从局部判断到全局范围约束的递归思想

一、题目描述

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

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

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

示例:

输入:

复制代码
root = [2,1,3]

输出:

复制代码
true

对应二叉树:

复制代码
       2
      / \
     1   3

满足:

复制代码
1 < 2 < 3

所以是有效二叉搜索树。

另一个例子:

输入:

复制代码
root = [5,1,4,null,null,3,6]

输出:

复制代码
false

结构:

复制代码
        5
       / \
      1   4
         / \
        3   6

虽然:

复制代码
3 < 4

但是:

复制代码
4 < 5

4 出现在 5 的右子树中,不满足:

复制代码
右子树所有节点 > 根节点

所以不是有效 BST。


二、为什么这道题值得学习?

这道题是二叉搜索树判断的经典题,也是面试高频题。

它考察三个核心:


1. 二叉搜索树性质

BST 满足:

复制代码
左子树节点 < 根节点 < 右子树节点

例如:

复制代码
        8
       / \
      3   10
     / \
    1   6

满足:

左边:

复制代码
1 < 3 < 6

右边:

复制代码
10 > 8

所以合法。


2. 不能只判断左右孩子

很多人第一反应:

判断:

复制代码
root.left < root
root.right > root

例如:

复制代码
        5
       / \
      1   7
         /
        4

看起来:

复制代码
1 < 5
7 > 5

好像正确。

但是:

复制代码
4 < 5

却出现在 5 的右子树。

所以:

❌ 只判断当前节点是不够的。

BST 的限制是:

所有子树节点都必须满足范围要求。


3. 全局范围约束思想

每个节点都有一个允许范围。

例如:

根节点:

复制代码
5

范围:

复制代码
(-∞,+∞)

右孩子:

复制代码
7

因为它在 5 的右边:

范围:

复制代码
(5,+∞)

7 的左孩子:

复制代码
4

它必须满足:

复制代码
5 < 4 < 7

不成立。

所以:

复制代码
false

三、核心思想:递归维护节点范围

验证 BST 的关键:

给每个节点传递:

复制代码
当前节点允许的最大值
当前节点允许的最小值

定义:

复制代码
isValid(node,min,max)

含义:

判断 node 是否满足:

复制代码
min < node.val < max

然后:

左子树:

最大值变成当前节点:

复制代码
isValid(node.left,min,node.val)

右子树:

最小值变成当前节点:

复制代码
isValid(node.right,node.val,max)

四、递归三部曲

1. 确定递归函数

定义:

复制代码
boolean isValid(TreeNode root,long min,long max)

含义:

判断当前节点是否在:

复制代码
(min,max)

范围内。


2. 确定递归终止条件

如果节点为空:

说明没有违反规则。

返回:

复制代码
true

代码:

复制代码
if(root == null){
    return true;
}

3. 确定单层递归逻辑

第一步:判断当前节点

如果:

复制代码
root.val <= min

或者:

复制代码
root.val >= max

说明违反 BST。

返回:

复制代码
false

第二步:递归左右子树

左子树:

范围:

复制代码
(min,root.val)

右子树:

范围:

复制代码
(root.val,max)

代码:

复制代码
return isValid(root.left,min,root.val)
&&
isValid(root.right,root.val,max);

五、解法:递归法(面试首选 ✅)

复制代码
class Solution {

    public boolean isValidBST(TreeNode root) {

        return check(root,Long.MIN_VALUE,Long.MAX_VALUE);

    }


    private boolean check(TreeNode root,long min,long max){

        // 空节点一定合法
        if(root == null){
            return true;
        }


        // 当前节点越界
        if(root.val <= min || root.val >= max){
            return false;
        }


        // 判断左右子树
        return check(root.left,min,root.val)
                &&
               check(root.right,root.val,max);
    }
}

六、过程图解

例如:

复制代码
        5
       / \
      1   7
         /
        6

第一次:

根节点:

复制代码
5

范围:

复制代码
(-∞,+∞)

满足。


左子树:

复制代码
1

范围:

复制代码
(-∞,5)

满足。


右子树:

复制代码
7

范围:

复制代码
(5,+∞)

满足。


7 的左节点:

复制代码
6

范围:

复制代码
(5,7)

满足。

最终:

复制代码
true

七、复杂度分析

时间复杂度:O(N)

原因:

每个节点访问一次。

所以:

复制代码
O(N)

空间复杂度:O(H)

递归调用栈取决于树高度。

平衡树:

复制代码
O(logN)

最坏链状树:

复制代码
O(N)

所以:

复制代码
O(H)

八、常见错误与避坑指南

❌ 错误一:只判断左右孩子

错误思想:

复制代码
root.left.val < root.val
root.right.val > root.val

例如:

复制代码
        5
       / \
      1   8
         /
        4

局部看:

复制代码
4 < 8

正确。

但是:

复制代码
4 < 5

不符合右子树要求。


❌ 错误二:使用 int 保存范围

错误:

复制代码
int min=Integer.MIN_VALUE;
int max=Integer.MAX_VALUE;

如果节点值刚好:

复制代码
-2147483648

会出现边界问题。

推荐:

复制代码
long

使用:

复制代码
Long.MIN_VALUE
Long.MAX_VALUE

❌ 错误三:没有处理重复值

BST 要求:

严格:

复制代码
左 < 根 < 右

所以:

复制代码
     5
    /
   5

不是 BST。

判断:

复制代码
<=
>=

不能写:

复制代码
<
>

九、另一种经典方法:中序遍历

二叉搜索树有一个重要性质:

中序遍历结果一定是严格递增数组。

例如:

复制代码
       2
      / \
     1   3

中序:

复制代码
1 2 3

递增。

所以可以:

遍历节点:

保存前一个节点值。

如果:

复制代码
当前值 <= 前一个值

说明不是 BST。

代码:

复制代码
class Solution {

    long pre = Long.MIN_VALUE;


    public boolean isValidBST(TreeNode root){

        if(root == null){
            return true;
        }


        if(!isValidBST(root.left)){
            return false;
        }


        if(root.val <= pre){
            return false;
        }


        pre = root.val;


        return isValidBST(root.right);
    }
}

十、面试高频追问

1️⃣ 为什么不能只比较左右孩子?

因为 BST 的限制是:

复制代码
整棵子树范围

不是:

复制代码
当前两个孩子

2️⃣ 为什么需要 long?

因为节点范围可能达到:

复制代码
Integer.MIN_VALUE
Integer.MAX_VALUE

使用 long 可以避免边界错误。


3️⃣ 两种方法哪个更好?

递归范围法:

优点:

  • 思路直观
  • 可以扩展到其他树约束问题

中序遍历:

优点:

  • 利用了 BST 特性
  • 代码更简洁

面试中两种都可以。


总结

LeetCode 98 的核心不是判断:

复制代码
左孩子 < 根 < 右孩子

而是维护:

复制代码
每个节点所在的合法范围

递归过程中不断缩小范围:

复制代码
根节点
 ↓
限制左右子树范围
 ↓
继续递归判断
相关推荐
学计算机的计算基1 小时前
回溯算法下篇:四道经典题讲透约束剪枝、原地标记、预计算与状态压缩
java·笔记·算法
霸道流氓气质2 小时前
SpringBoot中实现告警判定算法-位报警与模拟量阈值-技术详解
spring boot·算法·jquery
行者全栈架构师2 小时前
混元 Hy3 Agent 实战:季度报告 3 小时变 40 分钟
算法·架构·代码规范
兰令水2 小时前
hot100【acm版】【2026.7.21打卡-java版本】
java·开发语言·算法·leetcode·面试
好好沉淀3 小时前
主键选择(自增 vs UUID vs 雪花算法)
java·算法
退休倒计时3 小时前
【每日一题】LeetCode 131. 分割回文串 TypeScript
算法·leetcode·typescript
小白说大模型3 小时前
AI Agent 调试实战:链路追踪、Prompt 可视化与异常定位的系统方法
java·人工智能·python·算法·prompt
梅梅绵绵冰3 小时前
算法题-矩阵
数据结构·算法·矩阵
atunet3 小时前
关于从哈希冲突看缓存设计的实践与权衡7
算法