【力扣100题】33.验证二叉搜索树

一、题目描述

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

有效二叉搜索树定义:

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

示例

示例 输入 输出
示例1 root = [2,1,3] true
示例2 root = [5,1,4,null,null,3,6] false
复制代码
示例1:有效BST                    示例2:无效BST
    2                                 5
   / \                               / \
  1   3        ← 符合BST            1   4  ← 右子树包含3,不大于5
                                         / \
                                        3   6  ← 3不大于4,违反BST规则

提示

  • 树中节点数目范围在 [1, 10^4] 内
  • -2^31 <= Node.val <= 2^31 - 1

二、解题思路总览

方法 核心思想 时间复杂度 空间复杂度
递归(隐式栈) 中序遍历 + 前驱节点比较 O(n) O(h),h为树高

核心思想:

  • BST的中序遍历得到升序序列
  • 遍历过程中记录前驱节点,与当前节点比较
  • 如果前驱节点值 >= 当前节点值,说明不是升序,不是BST

为什么中序遍历?

  • BST的左子树 < 根 < 右子树
  • 中序遍历顺序:左-根-右,正好是升序
  • 升序中断就说明不是BST

三、完整代码

cpp 复制代码
class Solution {
public:
    TreeNode* pre = NULL;                   // 1. 记录前驱节点

    bool isValidBST(TreeNode* root) {
        if (!root) return true;              // 2. 空树是BST

        // 3. 递归检查左子树
        bool left = isValidBST(root->left);

        // 4. 比较前驱节点与当前节点
        // 中序遍历保证:左 < 根 < 右
        // 如果前驱 >= 当前,说明不是升序,不是BST
        if (pre != NULL && pre->val >= root->val) return false;

        // 5. 更新前驱为当前节点
        pre = root;

        // 6. 递归检查右子树
        bool right = isValidBST(root->right);

        // 7. 左子树和右子树都必须为BST
        return left && right;
    }
};

四、算法流程图(ASCII)

中序遍历验证过程

复制代码
示例1的BST:[2,1,3] 是有效的

    2
   / \
  1   3

中序遍历过程:
  遍历顺序:1 → 2 → 3(升序)
  pre的变化:NULL → 1 → 2 → 3
  每次比较:pre.val < current.val ✓

示例2的树:[5,1,4,null,null,3,6] 不是有效的BST

    5
   / \
  1   4
     / \
    3   6

中序遍历过程:
  遍历顺序:1 → 5 → 3 → 4 → 6
  pre的变化:NULL → 1 → 5 → 3
  发现问题:pre(5).val >= current(3).val ✗ → 返回false

递归展开过程(以示例1为例)

复制代码
isValidBST(2)
        │
        ├── isValidBST(1)  ← 左子树
        │       │
        │       ├── isValidBST(NULL) → true
        │       │
        │       ├── pre = NULL, root->val = 1
        │       │   pre != NULL? No → 继续
        │       │
        │       ├── pre = 1
        │       │
        │       └── isValidBST(NULL) → true
        │
        ├── pre = 1, root->val = 2
        │   pre != NULL? Yes
        │   pre.val(1) >= root.val(2)? No → 继续
        │
        ├── pre = 2
        │
        └── isValidBST(3)  ← 右子树
                │
                ├── isValidBST(NULL) → true
                │
                ├── pre = 2, root->val = 3
                │   pre != NULL? Yes
                │   pre.val(2) >= root.val(3)? No → 继续
                │
                └── pre = 3

最终返回:true && true && true = true

五、逐行解析

cpp 复制代码
class Solution {
public:
    // ─────────────────────────────────────────
    // 全局变量 pre:记录中序遍历的前驱节点
    // 初始化为 NULL
    // ─────────────────────────────────────────
    TreeNode* pre = NULL;

    // ─────────────────────────────────────────
    // 中序遍历验证BST
    // 左-根-右 的顺序保证遍历结果是升序
    // ─────────────────────────────────────────
    bool isValidBST(TreeNode* root) {
        // ─────────────────────────────────────────
        // 第1步:递归终止条件
        // 空树是有效的BST
        // ─────────────────────────────────────────
        if (!root) return true;

        // ─────────────────────────────────────────
        // 第2步:递归检查左子树
        // 左子树必须是BST
        // ─────────────────────────────────────────
        bool left = isValidBST(root->left);

        // ─────────────────────────────────────────
        // 第3步:比较前驱节点与当前节点
        //
        // 中序遍历保证遍历结果是升序
        // pre 是当前节点的前驱(中序遍历顺序)
        // 如果 pre.val >= root.val,说明不是升序
        // 不是升序就违反了BST的定义
        // ─────────────────────────────────────────
        if (pre != NULL && pre->val >= root->val) return false;

        // ─────────────────────────────────────────
        // 第4步:更新前驱节点
        // 当前节点成为下一个节点的前驱
        // ─────────────────────────────────────────
        pre = root;

        // ─────────────────────────────────────────
        // 第5步:递归检查右子树
        // 右子树必须是BST
        // ─────────────────────────────────────────
        bool right = isValidBST(root->right);

        // ─────────────────────────────────────────
        // 第6步:返回结果
        // 左子树、右子树、当前节点都符合BST定义才行
        // ─────────────────────────────────────────
        return left && right;
    }
};

六、复杂度分析

时间复杂度

分析 复杂度
每个节点访问一次 O(n)

推导:n 个节点,每个节点访问一次(比较、递归)。

空间复杂度

分析 复杂度
递归调用栈,最大深度为树高h O(h)

推导:

  • 最坏情况(链表形状):h = n,复杂度 O(n)
  • 平衡树情况:h = log n,复杂度 O(log n)

七、面试追问 FAQ

问题 回答
为什么用中序遍历? BST中序遍历是升序,升序中断就说明不是BST
pre 初始为什么是 NULL? 第一个节点没有前驱,不需要比较
为什么要判断 pre != NULL? 要先判断再访问 pre->val,避免空指针
可以用范围判断吗? 可以,记录每个节点的有效范围 min, max
如果节点值等于前驱值怎么办? 应该返回 false,因为BST要求严格大于

八、相关题目

题目 难度 关键点
98. 验证二叉搜索树 中等 本题
94. 二叉树的中序遍历 简单 中序遍历基础
230. 二叉搜索树中第K小的元素 中等 BST中序遍历
99. 恢复二叉搜索树 困难 中序遍历变形

九、总结

对比项 说明
代码行数 核心8行
时间复杂度 O(n)
空间复杂度 O(h)
递归顺序 中序遍历(左-根-右)
核心技巧 前驱节点比较

核心公式:

复制代码
BST的中序遍历 = 升序序列
验证方法:遍历过程中比较 pre.val < current.val

易错点:

  • 不能只比较左右子节点,要确保整个左子树都小于根,整个右子树都大于根
  • 使用前驱比较时,要先判断 pre != NULL

相关推荐
信奥卷王12 分钟前
[GESP202609 六级] 数组划分
数据结构·算法
牧羊人.33340 分钟前
动手学深度学习 07|Resnet网络与迁移学习
网络·人工智能·深度学习·神经网络·算法·cnn·迁移学习
All for pursuit.2 小时前
【并查集-2】399.除法求值
数据结构·c++·算法·leetcode
迪丽热爱2 小时前
一、C语言概述与程序结构
c语言·开发语言·算法
码爸2 小时前
动态规划介绍
算法·动态规划
怪奇云呼军3 小时前
教育试听预约后没到课,闪电智能 Voice Agent 如何做分层回访?
人工智能·python·算法·云计算·音视频
Nil2083 小时前
leetcode 416分割等和子集
算法·leetcode·职场和发展
云淡风轻~窗明几净3 小时前
角谷猜想的进展之三 移动了群山/沸騰的群山 2026-10-10
人工智能·算法·dubbo·图论
ForeverYang20153 小时前
一种无需训练缺陷数据生成算法--O2MAG
人工智能·深度学习·算法
Pniubi3 小时前
Agent开发:记忆系统中向量存储的合理安排
职场和发展