【LeetCode 热题 100】98. 验证二叉搜索树——(解法一)前序遍历
Problem: 98. 验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(N)
- 空间复杂度:O(H)
整体思路
这段代码旨在解决一个经典的树形结构问题:验证二叉搜索树 (Validate Binary Search Tree)。其目标是判断一棵给定的二叉树是否是有效的二叉搜索树(BST)。
一个有效的BST必须满足以下三个条件:
- 节点的左子树只包含键 小于 节点键的节点。
- 节点的右子树只包含键 大于 节点键的节点。
- 左右子树也必须分别是二叉搜索树。
仅仅检查 node.left.val < node.val
和 node.right.val > node.val
是不够的。因为一个节点的值不仅要和它的父节点比较,还必须满足其所有祖先节点施加的约束。例如,在一个节点的右子树中,所有节点的值都必须大于该节点的父节点的值,也要大于其祖父节点的值(如果该节点在祖父的右子树中)。
该算法采用了一种非常优雅且正确的 递归(深度优先搜索,DFS) 方法,通过传递有效值范围来解决这个问题。
-
核心思想:约束范围 (Constraint Propagation)
- 算法的核心思想是,对于树中的每一个节点,它不仅自身的值有要求,它还会对它的子树施加新的约束。
- 具体来说,当递归到一个节点
node
时,我们不仅检查它自身的值,还会将它的值作为新的边界传递给它的子节点。 - 对于
node
的左子树,所有节点的值都必须小于node.val
。所以,node.val
成为了左子树的上界 (right bound)。 - 对于
node
的右子树,所有节点的值都必须大于node.val
。所以,node.val
成为了右子树的下界 (left bound)。
-
递归与边界传递
- 算法的主体是一个递归函数
dfs(node, left, right)
,它负责验证以node
为根的子树是否是有效的BST,并且子树中所有节点的值都必须在开区间(left, right)
内。 - 数据类型选择:使用
long
类型的left
和right
是为了处理边界情况,即当节点的val
恰好是Integer.MIN_VALUE
或Integer.MAX_VALUE
时,普通的int
无法表示比它更小或更大的边界。 - 递归步骤:
a. 基本情况 (Base Case):如果node
为null
,空树被认为是有效的BST,直接返回true
。
b. 当前节点校验:检查当前节点的值x = node.val
是否满足从父节点继承来的约束,即x > left
并且x < right
。如果不满足,整棵树就不是BST,直接返回false
。
c. 递归校验子树:如果当前节点校验通过,则继续递归地检查左右子树,并传递更新后的约束:- 左子树:调用
dfs(node.left, left, x)
。左子树的下界left
不变,但上界更新为当前节点的值x
。 - 右子树:调用
dfs(node.right, x, right)
。右子树的上界right
不变,但下界更新为当前节点的值x
。
d. 结果合并:只有当当前节点、左子树和右子树都有效时,以node
为根的子树才被认为是有效的。&&
逻辑与操作完美地实现了这一点。
- 左子树:调用
- 算法的主体是一个递归函数
完整代码
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {/*** 判断给定的二叉树是否是一个有效的二叉搜索树(BST)。* @param root 二叉树的根节点* @return 如果是有效的BST则返回 true,否则返回 false*/public boolean isValidBST(TreeNode root) {// 初始调用,根节点的有效范围是 (Long.MIN_VALUE, Long.MAX_VALUE),即整个long类型的范围。// 使用 long 类型是为了防止节点的 val 恰好是 Integer.MIN_VALUE 或 Integer.MAX_VALUE 时,// 边界 int 类型无法表示比它更小或更大的数。return dfs(root, Long.MIN_VALUE, Long.MAX_VALUE);}/*** 递归辅助函数,验证以 node 为根的子树是否是有效的BST,* 并且其所有节点值都在开区间 (left, right) 内。* @param node 当前要验证的节点* @param left 允许的最小值(不包含)* @param right 允许的最大值(不包含)* @return 如果子树有效且满足范围约束,则返回 true*/private boolean dfs(TreeNode node, long left, long right) {// 基本情况:空树是有效的BSTif (node == null) {return true;}// 获取当前节点的值long x = node.val;// 核心逻辑:// 1. 检查当前节点值 x 是否在 (left, right) 区间内。// 2. 递归检查左子树,并更新上界为 x。左子树所有节点必须在 (left, x) 区间内。// 3. 递归检查右子树,并更新下界为 x。右子树所有节点必须在 (x, right) 区间内。// 只有这三个条件都满足时,才返回 true。return x < right && x > left && dfs(node.left, left, x) && dfs(node.right, x, right);}
}
时空复杂度
时间复杂度:O(N)
- 节点访问:算法通过深度优先搜索(DFS)的方式,访问了树中的每一个节点,并且每个节点只被访问一次。
- 递归函数调用:
dfs
函数对每个节点调用一次。在函数内部,执行的都是常数时间的比较和逻辑运算。 - 综合分析:
- 设树中共有
N
个节点。每个节点都被访问一次,并进行 O(1) 的操作。 - 因此,总的时间复杂度与节点总数成正比,为 O(N)。
- 设树中共有
空间复杂度:O(H)
- 主要存储开销:算法的额外空间主要由递归调用栈所占用。
- 递归深度:递归的最大深度取决于树的高度
H
。- 最好情况:如果树是高度平衡的,其高度
H
约为log N
。此时空间复杂度为 O(log N)。 - 最坏情况:如果树是极度不平衡的(例如,退化成一个链表),其高度
H
将等于节点数N
。此时空间复杂度为 O(N)。
- 最好情况:如果树是高度平衡的,其高度
综合分析:
该算法的(辅助)空间复杂度由递归栈的深度决定,因此为 O(H),其中 H
是树的高度。
【LeetCode 热题 100】98. 验证二叉搜索树——(解法二)中序遍历
参考灵神