一、题目描述
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3] 输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6] 输出:false 解释:根节点的值是 5 ,但是右子节点的值是 4 。
提示:
- 树中节点数目范围在
[1, 10^4]
内 -2^31 <= Node.val <= 2^31 - 1
二、解题思路
-
递归检查每个节点:对于每个节点,我们检查它的值是否在当前允许的范围内。这个范围是由它的父节点决定的。例如,对于左子节点,它的值应该小于其父节点的值,并且小于父节点允许的最大值;对于右子节点,它的值应该大于其父节点的值,并且大于父节点允许的最小值。
-
更新范围:在递归过程中,当我们向左子树递归时,最大值更新为当前节点的值;当我们向右子树递归时,最小值更新为当前节点的值。
-
边界条件:递归的边界条件是当前节点为空,这时返回
true
,因为空树也是有效的二叉搜索树。
三、具体代码
class Solution {
public boolean isValidBST(TreeNode root) {
return helper(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
private boolean helper(TreeNode node, long lower, long upper) {
if (node == null) {
return true;
}
if (node.val <= lower || node.val >= upper) {
return false;
}
// 对于左子树,最大值更新为当前节点的值;对于右子树,最小值更新为当前节点的值
return helper(node.left, lower, node.val) && helper(node.right, node.val, upper);
}
}
四、时间复杂度和空间复杂度
1. 时间复杂度
- 在这段代码中,
helper
函数对每个节点进行一次递归调用。 - 因此,时间复杂度与树中节点的数量成正比。
- 对于具有
N
个节点的二叉树,时间复杂度是O(N)
。
2. 空间复杂度
- 在这段代码中,递归调用栈的最大深度等于树的高度。
- 对于平衡的二叉搜索树,高度大约是
log(N)
,因此在这种情况下空间复杂度是O(log(N))
。 - 但是在最坏的情况下,树完全不平衡,每个节点都只有一个子节点,递归调用栈的深度将等于节点的数量
N
,因此空间复杂度是O(N)
。
五、总结知识点
-
二叉搜索树(BST):代码的目的是验证给定的二叉树是否符合BST的定义。BST是一种特殊的二叉树,其中每个节点的值都满足左子树的所有节点值小于该节点值,右子树的所有节点值大于该节点值。
-
递归:
helper
函数是一个递归函数,它用于遍历树的每个节点并检查它们是否满足BST的条件。递归是一种常用的算法技巧,它允许函数调用自身来解决问题的一个较小部分。 -
边界条件:在递归函数中,
node == null
是递归的边界条件,当达到这个条件时,递归停止并返回true
,因为空树或到达叶子节点的子树是有效的BST。 -
值范围检查:代码通过比较当前节点的值与传递给
helper
函数的上下边界lower
和upper
来检查每个节点的值是否在有效范围内。这是确保树中每个节点的值都在正确范围内的关键步骤。 -
参数传递和更新:在递归调用中,对于左子树,最大值被更新为当前节点的值,因为左子树的所有节点都必须小于当前节点的值;对于右子树,最小值被更新为当前节点的值,因为右子树的所有节点都必须大于当前节点的值。
-
逻辑与操作符(&&):在递归调用中使用
&&
操作符来确保同时满足左右子树的条件。这意味着如果左子树或右子树中的任何一个不满足BST的条件,整个表达式将返回false
。 -
整数边界:代码使用
Long.MIN_VALUE
和Long.MAX_VALUE
作为初始的上下边界,以处理整数的最小和最大可能值。这是为了避免整数溢出的问题,并确保所有可能的整数值都在考虑范围内。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。