forked from youngyangyang04/leetcode-master
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8360d40
commit 2fb0b7e
Showing
39 changed files
with
7,725 additions
and
164 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
|
||
|
||
## 98.验证二叉搜索树 | ||
|
||
题目地址:https://leetcode-cn.com/problems/validate-binary-search-tree/ | ||
|
||
|
||
给定一个二叉树,判断其是否是一个有效的二叉搜索树。 | ||
|
||
假设一个二叉搜索树具有如下特征: | ||
|
||
* 节点的左子树只包含小于当前节点的数。 | ||
* 节点的右子树只包含大于当前节点的数。 | ||
* 所有左子树和右子树自身必须也是二叉搜索树。 | ||
|
||
![98.验证二叉搜索树](https://img-blog.csdnimg.cn/20210203144334501.png) | ||
|
||
## 思路 | ||
|
||
要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。 | ||
|
||
有了这个特性,**验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。** | ||
|
||
## 递归法 | ||
|
||
可以递归中序遍历将二叉搜索树转变成一个数组,代码如下: | ||
|
||
``` | ||
vector<int> vec; | ||
void traversal(TreeNode* root) { | ||
if (root == NULL) 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 { | ||
private: | ||
vector<int> vec; | ||
void traversal(TreeNode* root) { | ||
if (root == NULL) return; | ||
traversal(root->left); | ||
vec.push_back(root->val); // 将二叉搜索树转换为有序数组 | ||
traversal(root->right); | ||
} | ||
public: | ||
bool isValidBST(TreeNode* root) { | ||
vec.clear(); // 不加这句在leetcode上也可以过,但最好加上 | ||
traversal(root); | ||
for (int i = 1; i < vec.size(); i++) { | ||
// 注意要小于等于,搜索树里不能有相同元素 | ||
if (vec[i] <= vec[i - 1]) return false; | ||
} | ||
return true; | ||
} | ||
}; | ||
``` | ||
|
||
以上代码中,我们把二叉树转变为数组来判断,是最直观的,但其实不用转变成数组,可以在递归遍历的过程中直接判断是否有序。 | ||
|
||
|
||
这道题目比较容易陷入两个陷阱: | ||
|
||
* 陷阱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: | ||
|
||
![二叉搜索树](https://img-blog.csdnimg.cn/20200812191501419.png) | ||
|
||
节点10小于左节点5,大于右节点15,但右子树里出现了一个6 这就不符合了! | ||
|
||
* 陷阱2 | ||
|
||
样例中最小节点 可能是int的最小值,如果这样使用最小的int来比较也是不行的。 | ||
|
||
此时可以初始化比较元素为longlong的最小值。 | ||
|
||
问题可以进一步演进:如果样例中根节点的val 可能是longlong的最小值 又要怎么办呢?文中会解答。 | ||
|
||
了解这些陷阱之后我们来看一下代码应该怎么写: | ||
|
||
递归三部曲: | ||
|
||
* 确定递归函数,返回值以及参数 | ||
|
||
要定义一个longlong的全局变量,用来比较遍历的节点是否有序,因为后台测试数据中有int最小值,所以定义为longlong的类型,初始化为longlong最小值。 | ||
|
||
注意递归函数要有bool类型的返回值, 我们在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg) 中讲了,只有寻找某一条边(或者一个节点)的时候,递归函数会有bool类型的返回值。 | ||
|
||
其实本题是同样的道理,我们在寻找一个不符合条件的节点,如果没有找到这个节点就遍历了整个树,如果找到不符合的节点了,立刻返回。 | ||
|
||
代码如下: | ||
|
||
``` | ||
long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值 | ||
bool isValidBST(TreeNode* root) | ||
``` | ||
|
||
* 确定终止条件 | ||
|
||
如果是空节点 是不是二叉搜索树呢? | ||
|
||
是的,二叉搜索树也可以为空! | ||
|
||
代码如下: | ||
|
||
``` | ||
if (root == NULL) 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; // 因为后台测试数据中有int最小值 | ||
bool isValidBST(TreeNode* root) { | ||
if (root == NULL) return true; | ||
bool left = isValidBST(root->left); | ||
// 中序遍历,验证遍历的元素是不是从小到大 | ||
if (maxVal < root->val) maxVal = root->val; | ||
else return false; | ||
bool right = isValidBST(root->right); | ||
return left && right; | ||
} | ||
}; | ||
``` | ||
|
||
以上代码是因为后台数据有int最小值测试用例,所以都把maxVal改成了longlong最小值。 | ||
|
||
如果测试数据中有 longlong的最小值,怎么办? | ||
|
||
不可能在初始化一个更小的值了吧。 建议避免 初始化最小值,如下方法取到最左面节点的数值来比较。 | ||
|
||
代码如下: | ||
|
||
``` | ||
class Solution { | ||
public: | ||
TreeNode* pre = NULL; // 用来记录前一个节点 | ||
bool isValidBST(TreeNode* root) { | ||
if (root == NULL) return true; | ||
bool left = isValidBST(root->left); | ||
if (pre != NULL && pre->val >= root->val) return false; | ||
pre = root; // 记录前一个节点 | ||
bool right = isValidBST(root->right); | ||
return left && right; | ||
} | ||
}; | ||
``` | ||
|
||
最后这份代码看上去整洁一些,思路也清晰。 | ||
|
||
## 迭代法 | ||
|
||
可以用迭代法模拟二叉树中序遍历,对前中后序迭代法生疏的同学可以看这两篇[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg),[二叉树:前中后序迭代方式统一写法](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg) | ||
|
||
迭代法中序遍历稍加改动就可以了,代码如下: | ||
|
||
``` | ||
class Solution { | ||
public: | ||
bool isValidBST(TreeNode* root) { | ||
stack<TreeNode*> st; | ||
TreeNode* cur = root; | ||
TreeNode* pre = NULL; // 记录前一个节点 | ||
while (cur != NULL || !st.empty()) { | ||
if (cur != NULL) { | ||
st.push(cur); | ||
cur = cur->left; // 左 | ||
} else { | ||
cur = st.top(); // 中 | ||
st.pop(); | ||
if (pre != NULL && cur->val <= pre->val) | ||
return false; | ||
pre = cur; //保存前一个访问的结点 | ||
cur = cur->right; // 右 | ||
} | ||
} | ||
return true; | ||
} | ||
}; | ||
``` | ||
|
||
在[二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg)中我们分明写出了痛哭流涕的简洁迭代法,怎么在这里不行了呢,因为本题是要验证二叉搜索树啊。 | ||
|
||
## 总结 | ||
|
||
这道题目是一个简单题,但对于没接触过的同学还是有难度的。 | ||
|
||
所以初学者刚开始学习算法的时候,看到简单题目没有思路很正常,千万别怀疑自己智商,学习过程都是这样的,大家智商都差不多,哈哈。 | ||
|
||
只要把基本类型的题目都做过,总结过之后,思路自然就开阔了。 | ||
|
||
|
Oops, something went wrong.