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

相关推荐
ZhengEnCi5 小时前
Agent 架构怎么选:从单 Agent 到多 Agent 编排的选型决策指南
算法·架构
秋天的怀念_David6 小时前
第二十二夜 · 数字迷雾
java·程序人生·算法
Pniubi6 小时前
力扣283移动零(双指针法)
算法·leetcode·职场和发展
hhb_6187 小时前
大模型落地三重优化实战
人工智能·算法·机器学习
云和数据.ChenGuang7 小时前
光哥说Python RAG 系统需要哪些评估指标:从检索到生成的全链路评测指南
网络·人工智能·python·算法·langchain
2601_970703838 小时前
工业除湿机租赁:把一份租金合同拆开看
大数据·数据结构·算法·数据库架构·推荐算法
心中有你02148 小时前
Java实现Dijkstra最短路径算法(邻接表 + 优先队列)
java·数据库·算法
ZhengEnCi9 小时前
同样的模型,为什么本地和云 API 差别那么大:量化精度、聊天模板与采样参数的三大元凶
人工智能·算法·llm
Flynn Liang10 小时前
RAG 答非所问/幻觉怎么破?Spring AI QuestionAnswerAdvisor 召回优化实战(TopK/重排/混合检索)
人工智能·算法·spring·rag·幻觉·springai
liminla!10 小时前
SQL 从入门到进阶:语法速查与实战练习(完整优化版)
数据库·sql·算法