【力扣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

相关推荐
微风欲寻竹影20 小时前
Java数据结构——二叉树(Binary Tree)详解
java·数据结构·算法
想吃火锅100520 小时前
【leetcode】3.无重复字符的最长字串js版
算法·leetcode·职场和发展
smith成长之旅20 小时前
08 | Mem0 框架分析: BM25 的 Sigmoid 归一化
数据库·python·算法
dongf201920 小时前
R 语言随机森林算法
算法·随机森林·r语言
AZaLEan__21 小时前
图论:拓扑排序
算法·深度优先
悠仁さん21 小时前
数据结构 排序
数据结构·算法·排序算法
阿文的代码库21 小时前
机器学习之精确率和召回率的关系
人工智能·算法·机器学习
Raink老师21 小时前
【AI面试临阵磨枪-100】Harness 与 MCP/A2A 协议、Skill 体系如何集成?
人工智能·面试·职场和发展
我爱cope21 小时前
【Agent智能体21 | 构建AI工作流的技巧-优化组件的常用方法】
人工智能·设计模式·语言模型·职场和发展
咸鱼翻身小阿橙21 小时前
高斯模糊降噪/磨皮算法降噪图像
前端·opencv·算法·webpack·c#