LeetCode 98. 验证二叉搜索树
1、题目
题目链接:98. 验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3]
输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
提示:
- 树中节点数目范围在[1, 104] 内
- -231 <= Node.val <= 231 - 1
2、递归
思路
要解决这道题首先我们要了解二叉搜索树有什么性质可以给我们利用,由题目给出的信息我们可以知道:如果该二叉树的左子树不为空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;它的左右子树也为二叉搜索树。
在中序遍历下,输出的二叉搜索树节点的数值是有序序列。
有了这个特性,验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。
可以递归中序遍历将二叉搜索树转变成一个数组,代码如下:
vector<int> vec;
void traversal(TreeNode* root) {
if (root == nullptr) {
return;
}
traversal(root->left);
// 将二叉搜索树转换为有序数组
vec.push_back(root->val);
traversal(root->right);
}
然后只要比较一下,这个数组是否是有序的,注意二叉搜索树中不能有重复元素。
traversal(root);
for (int i = 1; i < vec.size(); i++) {
// 注意要小于等于,搜索树里不能有相同元素
if (vec[i] <= vec[i - 1]) return false;
}
return true;
代码
class Solution {
public:
vector<int> vec;
void traversal(TreeNode* root) {
if (root == nullptr) {
return;
}
// 递归遍历左子树
traversal(root->left);
// 将二叉搜索树转换为有序数组
vec.push_back(root->val);
// 递归遍历右子树
traversal(root->right);
}
bool isValidBST(TreeNode* root) {
// 不加这句在leetcode上也可以过,但最好加上
vec.clear();
traversal(root);
// 遍历数组,检查节点值是否递增
for (int i = 1; i < vec.size(); i++) {
// 注意要小于等于,搜索树里不能有相同元素
if (vec[i] <= vec[i - 1]) {
return false;
}
}
return true;
}
};
复杂度分析
- 时间复杂度: O(n)
- 空间复杂度: O(n)
3、递归(优化)
思路
以上代码中,我们把二叉树转变为数组来判断,是最直观的,但其实不用转变成数组,可以在递归遍历的过程中直接判断是否有序。
这道题目比较容易陷入两个陷阱:
- 陷阱1
不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了。
写出了类似这样的代码:
if (root->val > root->left->val && root->val < root->right->val) {
return true;
} else {
return false;
}
我们要比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点。所以以上代码的判断逻辑是错误的。
例如: [10,5,15,null,null,6,20] 这个case:
节点10大于左节点5,小于右节点15,但右子树里出现了一个6 这就不符合了!
- 陷阱2
样例中最小节点 可能是int的最小值,如果这样使用最小的int来比较也是不行的。
此时可以初始化比较元素为longlong的最小值。
问题可以进一步演进:如果样例中根节点的val 可能是longlong的最小值 又要怎么办呢?文中会解答。
了解这些陷阱之后我们来看一下代码应该怎么写:
递归三部曲:
- 确定递归函数,返回值以及参数
要定义一个longlong的全局变量,用来比较遍历的节点是否有序,因为后台测试数据中有int最小值,所以定义为longlong的类型,初始化为longlong最小值。
注意递归函数要有bool类型的返回值,只有寻找某一条边(或者一个节点)的时候,递归函数会有bool类型的返回值。
其实本题是同样的道理,我们在寻找一个不符合条件的节点,如果没有找到这个节点就遍历了整个树,如果找到不符合的节点了,立刻返回。
代码如下:
long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
bool isValidBST(TreeNode* root)
- 确定终止条件
如果是空节点 是不是二叉搜索树呢?
是的,二叉搜索树也可以为空!
代码如下:
if (root == nullptr) return true;
- 确定单层递归的逻辑
中序遍历,一直更新maxVal,一旦发现maxVal >= root->val,就返回false,注意元素相同时候也要返回false。
代码如下:
bool left = isValidBST(root->left); // 左
// 中序遍历,验证遍历的元素是不是从小到大
if (maxVal < root->val) maxVal = root->val; // 中
else return false;
bool right = isValidBST(root->right); // 右
return left && right;
整体代码如下:
class Solution {
public:
long long maxVal = LONG_MIN;
bool isValidBST(TreeNode* root) {
if (root == nullptr) {
return true;
}
// 递归判断左子树是否满足二叉搜索树的条件
bool left = isValidBST(root->left);
// 中序遍历,验证遍历的元素是不是从小到大
// 如果当前节点的值小于maxVal,则更新maxVal为当前节点的值
if (maxVal < root->val) {
maxVal = root->val;
} else {
// 否则返回false,表示不满足二叉搜索树的条件
return false;
}
// 递归判断右子树是否满足二叉搜索树的条件
bool right = isValidBST(root->right);
// 返回左子树和右子树都满足条件的结果
return left && right;
}
};
以上代码是因为后台数据有int最小值测试用例,所以都把maxVal改成了longlong最小值。
如果测试数据中有 longlong的最小值,怎么办?
不可能在初始化一个更小的值了吧。 建议避免 初始化最小值,如下方法取到最左面节点的数值来比较。
代码如下:
代码
class Solution {
public:
TreeNode* pre = nullptr;
bool isValidBST(TreeNode* root) {
if (root == nullptr) {
return true;
}
// 递归判断左子树是否为有效二叉搜索树
bool left = isValidBST(root->left);
// 判断当前节点的值是否大于前一个节点的值(若前一个节点存在)
if (pre != nullptr && pre->val >= root->val) {
return false;
}
// 更新前一个节点为当前节点
pre = root;
// 递归判断右子树是否为有效二叉搜索树
bool right = isValidBST(root->right);
// 返回左子树和右子树都为有效二叉搜索树的结果
return left && right;
}
};
复杂度分析
- 时间复杂度: O(n)
- 空间复杂度: O(n)
3、迭代
思路
代码
class Solution {
public:
bool isValidBST(TreeNode* root) {
stack<TreeNode*> stk;
TreeNode* cur = root;
TreeNode* pre = nullptr;
// 遍历二叉树
while (!stk.empty() || cur != nullptr) {
// 当前节点不为空,将其入栈,并将当前节点指向其左子节点
if (cur != nullptr) {
stk.push(cur);
cur = cur->left;
} else {
// 当前节点为空,出栈,取出栈顶元素作为当前节点
cur = stk.top();
stk.pop();
// 判断当前节点和前一个节点(非空)的值大小关系
if (pre != nullptr && cur->val <= pre->val) {
return false;
}
// 更新前一个节点为当前节点
pre = cur;
// 将当前节点指向其右子节点
cur = cur->right;
}
}
return true;
}
};
复杂度分析
- 时间复杂度: O(n)
- 空间复杂度: O(n)