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 的核心不是判断:

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

而是维护:

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

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

复制代码
根节点
 ↓
限制左右子树范围
 ↓
继续递归判断
相关推荐
江畔柳前堤6 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore7 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考8 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX9 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面10 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号10 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽10 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI10 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者12 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者12 小时前
【征程6】校准量化中HistogramObserver解析
算法