LeetCode 98 Validate Binary Search Tree 深度解析

前言:这道题到底在考什么?

LeetCode 98「Validate Binary Search Tree」要求:给定一棵二叉树,判断它是不是一棵有效的二叉搜索树(BST)。leetcode

有效 BST 的核心性质是:

  • 任意节点的左子树所有节点值都严格小于该节点值。
  • 任意节点的右子树所有节点值都严格大于该节点值。
  • 左右子树本身也必须是 BST。geeksforgeeks+1

看起来很简单,但很多人一开始都会掉进一个经典坑:只检查"父节点和它的左右孩子"这三者之间的大小关系。

常见误解:只比较父节点和孩子

很多初学者(包括一开始的我们)会这样想:

  • 用 DFS 或后序遍历二叉树。
  • 在每个节点处判断:left.val < node.val < right.val。
  • 同时递归检查左右子树。

问题是:

这种做法只保证"每个节点相对于它的直接孩子"是对的,却没有保证"子树中更深层节点"满足整条路径上所有祖先节点的约束。afteracademy+1​

一个经典反例:algomap+1​

根:5

右子:6

左子:3

结构是:

复制代码
    5
  null
      6
    3
      7(可有可无)

逐层看:

  • 3 < 6,没问题。
  • 6 > 5,也没问题。

如果只看"父子关系",这个树会被错误判为合法 BST。

但实际上,3 位于 5 的右子树中,就必须同时满足:3 > 5,这一点被完全漏掉了。

这个反例说明:

BST 的性质是"全局路径约束",而不仅是"局部父子约束"。enjoyalgorithms

正确视角:把「合法区间」往下传

解决这个问题的关键思想是:对每个节点,不仅要看它相对父节点的位置,还要维护它"允许存在的值范围":

  • 每个节点的值必须落在某个区间 (min, max) 内。
  • 这个区间来自于它上面所有祖先的约束,被一层层"收紧"。hellointerview+2

具体来说:

  • 对根节点:一开始没有任何限制,可以认为区间是 (-∞, +∞)。
  • 对某个节点:
    • 走到它的左子节点时:左子节点值必须小于当前节点值,同时还要大于原来的 min,所以左子树的区间变为 (min, node.val)。
    • 走到它的右子节点时:右子节点值必须大于当前节点值,同时还要小于原来的 max,所以右子树的区间变为 (node.val, max)。geeksforgeeks+1

这样一来,每个节点都被迫同时满足"相对于所有祖先"的大小关系,而不只是父节点。

开区间 vs 闭区间:为什么要「严格」?

题目里写的是 strictly less 和 strictly greater,意思是:leetcode

  • 左子树所有节点的值必须严格小于当前节点值。
  • 右子树所有节点的值必须严格大于当前节点值。

这意味着在设计区间时,应该使用"开区间"而不是"闭区间":

  • 节点值要满足:min < node.val < max。
  • 对左子树:新的 max 是当前节点值 → (min, node.val)。
  • 对右子树:新的 min 是当前节点值 → (node.val, max)。dev+1

如果错用闭区间,例如允许 node.val == min 或 node.val == max,就会允许"值相等"的情况,这与题目要求不符。

初始上下界:为什么需要「没有下界 / 没有上界」?

一开始有个直觉问题:

"既然节点范围要用 (min, max) 表示,那根节点的初始 min 和 max 是什么?"

有几种常见想法:

  1. 写死为题目给定的数值范围:[-2³¹, 2³¹-1]。

    • 这样确实可以跑通这一题,但可扩展性很差,一旦换成别的题目或别的类型(比如 long long)就需要修改。
  2. 更通用、也更常见的一种写法是:

    • 用 null / None 来表示"没有下界"或"没有上界",递归时按需更新。dev+1
    • 在检查时,只有当 min / max 非空时才做比较。

这其实也更贴近递归的自然含义:

根节点一开始确实没有任何限制,约束是从根开始逐步往下"收紧"出来的,而不是一开始就被一个死的固定区间限制住。

递归函数的设计与 base case

结合上面的讨论,一个典型的递归函数可以设计成这样(伪代码思路):hellointerview+2​

函数签名:

复制代码
bool dfs(node, min, max)

逻辑大致分三步:

  1. base case

    • 如果 node 为 null,说明子树为空,是合法 BST,返回 true。
  2. 检查当前节点是否在合法区间内

    • 如果 min 不为空并且 node.val <= min,返回 false。
    • 如果 max 不为空并且 node.val >= max,返回 false。
  3. 递归检查左右子树

    • 左子树:dfs(node.left, min, node.val)。
    • 右子树:dfs(node.right, node.val, max)。
    • 两者都为 true 才整体为 true。

这样就完整实现了"约束区间向下传递 + 严格大小关系"的思想。algomap+1​

顺带理解一下递归和「从叶子往上」

在这个题里,我们经常会用到 DFS + 递归。理解递归时,可以同时从两个角度看它的"方向":enjoyalgorithms+1​

  • 调用顺序上

    • 函数从根节点开始调用,一路往下到某个叶子,调用栈越来越深,然后再一层层返回到根节点。geeksforgeeks+1
  • 执行顺序上(以 postorder 为例):

    • 代码里"写在递归调用之后的逻辑",是在子树都处理完之后执行的,所以是"叶子先处理,再到父节点",看起来像是"从下往上"的过程。stackoverflow+1

在 Validate BST 的这个区间解法里,检查逻辑其实发生在"下探到某个节点时",然后再将更紧的区间交给子节点,既有自顶向下传约束,也有自底向上返回结果,两者结合。

小结

这道题真正考的不是"会不会写树的遍历",而是:

  • 能不能正确理解 BST 的"全局路径约束",而不是只看局部父子关系。enjoyalgorithms
  • 能不能想到"用区间 (min, max) 来表示约束,并在递归中一层层传下去"。geeksforgeeks+2
  • 能否注意到"strictly less / greater"映射到代码里就是"开区间 + 严格比较"。
  • 对递归和调用栈有直觉:从根往下调用,从叶子往上返回,中间通过参数传约束,通过返回值传真假。enjoyalgorithms+1

如果已经理解到这里,这道题你不仅是"能做对",而且是"吃透了"。

相关推荐
Ayanami_Reii1 小时前
进阶数据结构应用-线段树扫描线
数据结构·算法·线段树·树状数组·离散化·fenwick tree·线段树扫描线
水木姚姚1 小时前
C++ begin
开发语言·c++·算法
浅川.251 小时前
xtuoj 素数个数
数据结构·算法
jyyyx的算法博客1 小时前
LeetCode 面试题 16.18. 模式匹配
算法·leetcode
uuuuuuu1 小时前
数组中的排序问题
算法
Stream1 小时前
加密与签名技术之密钥派生与密码学随机数
后端·算法
Stream1 小时前
加密与签名技术之哈希算法
后端·算法
少许极端2 小时前
算法奇妙屋(十五)-BFS解决边权为1的最短路径问题
数据结构·算法·bfs·宽度优先·队列·图解算法·边权为1的最短路径问题
机械电气电机杂谈2 小时前
电机热电偶原理与应用
职场和发展·创业创新·制造·学习方法·业界资讯