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

相关推荐
SimpleLearingAI1 小时前
聚类算法详解
算法·数据挖掘·聚类
刀法如飞2 小时前
Go 字符串查找的 20 种实现方式,用不同思路解决问题
算法·面试·程序员
Dlrb12114 小时前
C语言-指针数组与数组指针
c语言·数据结构·算法·指针·数组指针·指针数组·二级指针
WL_Aurora4 小时前
Python 算法基础篇之集合
python·算法
平行侠4 小时前
A15 工业路由器IP前缀高速检索与内存压缩系统
网络·tcp/ip·算法
阿旭超级学得完5 小时前
C++11包装器(function和bind)
java·开发语言·c++·算法·哈希算法·散列表
li星野5 小时前
位运算 & 数学 & 高频进阶九题通关(Python + C++)
c++·python·学习·算法
jerryinwuhan5 小时前
hello算法,简单讲(1)
算法·排序算法
y = xⁿ5 小时前
20天速通LeetCodeday15:BFS广度优先搜索
算法·宽度优先