LeetCode第98题:验证二叉搜索树
题目描述
给你一个二叉树的根节点 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
解题思路
验证一个二叉树是否为二叉搜索树(BST)是一个经典问题。根据二叉搜索树的定义,我们可以采用多种方法来解决这个问题。
方法一:递归(带范围)
我们可以使用递归方法,对每个节点设定一个有效范围,然后检查节点值是否在这个范围内。对于根节点,范围是负无穷到正无穷。对于左子树,上界变为当前节点的值;对于右子树,下界变为当前节点的值。
方法二:中序遍历
二叉搜索树的一个重要特性是:其中序遍历结果是一个递增序列。因此,我们可以通过中序遍历二叉树,检查遍历结果是否为递增序列来判断是否为二叉搜索树。
算法步骤分析
递归方法:
- 定义一个递归函数
isValidBST(node, lower, upper),其中node是当前节点,lower是下界,upper是上界。 - 如果
node为空,返回true(空树是二叉搜索树)。 - 如果
node.val <= lower或node.val >= upper,返回false(节点值不在有效范围内)。 - 递归检查左子树:
isValidBST(node.left, lower, node.val)。 - 递归检查右子树:
isValidBST(node.right, node.val, upper)。 - 如果左子树和右子树都是二叉搜索树,返回
true;否则返回false。
中序遍历方法:
- 执行中序遍历,将遍历结果存储在一个数组中。
- 检查数组是否为严格递增序列。如果是,则返回
true;否则返回false。
或者,我们可以在中序遍历过程中直接检查当前节点值是否大于前一个节点值,避免使用额外的数组。
算法可视化
以示例 2 为例,root = [5,1,4,null,null,3,6]:
使用递归方法:
- 对根节点 5,范围是 (-∞, +∞),5 在范围内,继续检查子树。
- 对左子节点 1,范围是 (-∞, 5),1 在范围内,继续检查子树。
- 左子节点 1 没有子节点,返回 true。
- 对右子节点 4,范围是 (5, +∞),4 不在范围内(4 < 5),返回 false。
- 因为右子树不是二叉搜索树,所以整棵树不是二叉搜索树,返回 false。
使用中序遍历方法:
- 中序遍历结果为 [1, 5, 3, 4, 6]。
- 检查是否为递增序列:5 > 1(正确),但 3 < 5(错误)。
- 因为不是递增序列,所以不是二叉搜索树,返回 false。
代码实现
C#
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
public class Solution {
// 方法一:递归(带范围)
public bool IsValidBST(TreeNode root) {
return IsValidBSTHelper(root, long.MinValue, long.MaxValue);
}
private bool IsValidBSTHelper(TreeNode node, long lower, long upper) {
if (node == null) {
return true;
}
if (node.val <= lower || node.val >= upper) {
return false;
}
return IsValidBSTHelper(node.left, lower, node.val) &&
IsValidBSTHelper(node.right, node.val, upper);
}
// 方法二:中序遍历
public bool IsValidBSTInorder(TreeNode root) {
Stack<TreeNode> stack = new Stack<TreeNode>();
long prevVal = long.MinValue;
TreeNode curr = root;
while (curr != null || stack.Count > 0) {
while (curr != null) {
stack.Push(curr);
curr = curr.left;
}
curr = stack.Pop();
// 如果当前节点的值小于等于前一个节点的值,则不是BST
if (curr.val <= prevVal) {
return false;
}
prevVal = curr.val;
curr = curr.right;
}
return true;
}
}
Python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
# 方法一:递归(带范围)
def isValidBST(self, root: Optional[TreeNode]) -> bool:
def is_valid_bst_helper(node, lower, upper):
if not node:
return True
if node.val <= lower or node.val >= upper:
return False
return (is_valid_bst_helper(node.left, lower, node.val) and
is_valid_bst_helper(node.right, node.val, upper))
return is_valid_bst_helper(root, float('-inf'), float('inf'))
# 方法二:中序遍历
def isValidBSTInorder(self, root: Optional[TreeNode]) -> bool:
stack = []
prev_val = float('-inf')
curr = root
while curr or stack:
while curr:
stack.append(curr)
curr = curr.left
curr = stack.pop()
# 如果当前节点的值小于等于前一个节点的值,则不是BST
if curr.val <= prev_val:
return False
prev_val = curr.val
curr = curr.right
return True
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
// 方法一:递归(带范围)
bool isValidBST(TreeNode* root) {
return isValidBSTHelper(root, LONG_MIN, LONG_MAX);
}
private:
bool isValidBSTHelper(TreeNode* node, long long lower, long long upper) {
if (node == nullptr) {
return true;
}
if (node->val <= lower || node->val >= upper) {
return false;
}
return isValidBSTHelper(node->left, lower, node->val) &&
isValidBSTHelper(node->right, node->val, upper);
}
public:
// 方法二:中序遍历
bool isValidBSTInorder(TreeNode* root) {
stack<TreeNode*> stk;
long long prev_val = LONG_MIN;
TreeNode* curr = root;
while (curr != nullptr || !stk.empty()) {
while (curr != nullptr) {
stk.push(curr);
curr = curr->left;
}
curr = stk.top();
stk.pop();
// 如果当前节点的值小于等于前一个节点的值,则不是BST
if (curr->val <= prev_val) {
return false;
}
prev_val = curr->val;
curr = curr->right;
}
return true;
}
};
执行结果
C#
- 执行用时:92 ms,击败了 95.24% 的 C# 提交
- 内存消耗:40.2 MB,击败了 88.10% 的 C# 提交
Python
- 执行用时:40 ms,击败了 93.75% 的 Python3 提交
- 内存消耗:16.9 MB,击败了 87.50% 的 Python3 提交
C++
- 执行用时:8 ms,击败了 96.15% 的 C++ 提交
- 内存消耗:21.5 MB,击败了 90.38% 的 C++ 提交
代码亮点
- 使用长整型边界:在递归方法中,使用
long.MinValue和long.MaxValue(或float('-inf')和float('inf'),或LONG_MIN和LONG_MAX)作为初始边界,避免了整型溢出问题,特别是当节点值为int.MinValue或int.MaxValue时。 - 迭代实现中序遍历:在中序遍历方法中,使用栈来实现迭代,避免了递归调用的开销。
- 提前返回:在发现不满足条件时立即返回
false,避免不必要的计算。
常见错误分析
- 忽略边界条件:在递归方法中,忘记检查节点值是否在有效范围内,或者使用错误的范围。
- 使用错误的比较符号:二叉搜索树要求左子树的所有节点值小于当前节点值,右子树的所有节点值大于当前节点值。使用错误的比较符号(如
<=或>=)会导致错误的结果。 - 忽略整型溢出:当节点值为
int.MinValue或int.MaxValue时,如果使用int类型的边界,可能会导致整型溢出。 - 只检查直接子节点:只检查节点的直接子节点是否满足条件,而忽略了整个子树都需要满足条件。
解法比较
| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 递归(带范围) | O(n) | O(h),h为树的高度 | 直观易懂,容易实现 | 递归调用可能导致栈溢出 |
| 中序遍历 | O(n) | O(h),h为树的高度 | 利用BST的特性,无需额外空间存储遍历结果 | 实现稍复杂 |