leetcode-master/problems/0098.验证二叉搜索树.md

533 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="https://code-thinking-1253855093.file.myqcloud.com/pics/20210924105952.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 98.验证二叉搜索树
[力扣题目链接](https://leetcode-cn.com/problems/validate-binary-search-tree/)
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
* 节点的左子树只包含小于当前节点的数。
* 节点的右子树只包含大于当前节点的数。
* 所有左子树和右子树自身必须也是二叉搜索树。
![98.验证二叉搜索树](https://img-blog.csdnimg.cn/20210203144334501.png)
# 思路
要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。
有了这个特性,**验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。**
## 递归法
可以递归中序遍历将二叉搜索树转变成一个数组,代码如下:
```CPP
vector<int> vec;
void traversal(TreeNode* root) {
if (root == NULL) return;
traversal(root->left);
vec.push_back(root->val); // 将二叉搜索树转换为有序数组
traversal(root->right);
}
```
然后只要比较一下,这个数组是否是有序的,**注意二叉搜索树中不能有重复元素**。
```CPP
traversal(root);
for (int i = 1; i < vec.size(); i++) {
// 注意要小于等于,搜索树里不能有相同元素
if (vec[i] <= vec[i - 1]) return false;
}
return true;
```
整体代码如下:
```CPP
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://programmercarl.com/0112.路径总和.html) 中讲了只有寻找某一条边或者一个节点的时候递归函数会有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;
```
整体代码如下:
```CPP
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的最小值怎么办
不可能在初始化一个更小的值了吧。 建议避免 初始化最小值,如下方法取到最左面节点的数值来比较。
代码如下:
```CPP
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://programmercarl.com/二叉树的迭代遍历.html)[二叉树:前中后序迭代方式统一写法](https://programmercarl.com/二叉树的统一迭代法.html)
迭代法中序遍历稍加改动就可以了,代码如下:
```CPP
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://programmercarl.com/0700.二叉搜索树中的搜索.html)中我们分明写出了痛哭流涕的简洁迭代法,怎么在这里不行了呢,因为本题是要验证二叉搜索树啊。
# 总结
这道题目是一个简单题,但对于没接触过的同学还是有难度的。
所以初学者刚开始学习算法的时候,看到简单题目没有思路很正常,千万别怀疑自己智商,学习过程都是这样的,大家智商都差不多,哈哈。
只要把基本类型的题目都做过,总结过之后,思路自然就开阔了。
# 其他语言版本
## Java
```Java
class Solution {
// 递归
TreeNode max;
public boolean isValidBST(TreeNode root) {
if (root == null) {
return true;
}
// 左
boolean left = isValidBST(root.left);
if (!left) {
return false;
}
// 中
if (max != null && root.val <= max.val) {
return false;
}
max = root;
// 右
boolean right = isValidBST(root.right);
return right;
}
}
class Solution {
// 迭代
public boolean isValidBST(TreeNode root) {
if (root == null) {
return true;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode pre = null;
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;// 左
}
// 中,处理
TreeNode pop = stack.pop();
if (pre != null && pop.val <= pre.val) {
return false;
}
pre = pop;
root = pop.right;// 右
}
return true;
}
}
// 简洁实现·递归解法
class Solution {
public boolean isValidBST(TreeNode root) {
return validBST(Long.MIN_VALUE, Long.MAX_VALUE, root);
}
boolean validBST(long lower, long upper, TreeNode root) {
if (root == null) return true;
if (root.val <= lower || root.val >= upper) return false;
return validBST(lower, root.val, root.left) && validBST(root.val, upper, root.right);
}
}
// 简洁实现·中序遍历
class Solution {
private long prev = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root == null) {
return true;
}
if (!isValidBST(root.left)) {
return false;
}
if (root.val <= prev) { // 不满足二叉搜索树条件
return false;
}
prev = root.val;
return isValidBST(root.right);
}
}
```
## Python
**递归** - 利用BST中序遍历特性,把树"压缩"成数组
```python
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
# 思路: 利用BST中序遍历的特性.
# 中序遍历输出的二叉搜索树节点的数值是有序序列
candidate_list = []
def __traverse(root: TreeNode) -> None:
nonlocal candidate_list
if not root:
return
__traverse(root.left)
candidate_list.append(root.val)
__traverse(root.right)
def __is_sorted(nums: list) -> bool:
for i in range(1, len(nums)):
if nums[i] <= nums[i - 1]: # ⚠️ 注意: Leetcode定义二叉搜索树中不能有重复元素
return False
return True
__traverse(root)
res = __is_sorted(candidate_list)
return res
```
**递归** - 标准做法
```python
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
# 规律: BST的中序遍历节点数值是从小到大.
cur_max = -float("INF")
def __isValidBST(root: TreeNode) -> bool:
nonlocal cur_max
if not root:
return True
is_left_valid = __isValidBST(root.left)
if cur_max < root.val:
cur_max = root.val
else:
return False
is_right_valid = __isValidBST(root.right)
return is_left_valid and is_right_valid
return __isValidBST(root)
```
```python
迭代-中序遍历
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
stack = []
cur = root
pre = None
while cur or stack:
if cur: # 指针来访问节点,访问到最底层
stack.append(cur)
cur = cur.left
else: # 逐一处理节点
cur = stack.pop()
if pre and cur.val <= pre.val: # 比较当前节点和前节点的值的大小
return False
pre = cur
cur = cur.right
return True
```
## Go
```Go
import "math"
func isValidBST(root *TreeNode) bool {
// 二叉搜索树也可以是空树
if root == nil {
return true
}
// 由题目中的数据限制可以得出min和max
return check(root,math.MinInt64,math.MaxInt64)
}
func check(node *TreeNode,min,max int64) bool {
if node == nil {
return true
}
if min >= int64(node.Val) || max <= int64(node.Val) {
return false
}
// 分别对左子树和右子树递归判断如果左子树和右子树都符合则返回true
return check(node.Right,int64(node.Val),max) && check(node.Left,min,int64(node.Val))
}
```
```go
// 中序遍历解法
func isValidBST(root *TreeNode) bool {
// 保存上一个指针
var prev *TreeNode
var travel func(node *TreeNode) bool
travel = func(node *TreeNode) bool {
if node == nil {
return true
}
leftRes := travel(node.Left)
// 当前值小于等于前一个节点的值返回false
if prev != nil && node.Val <= prev.Val {
return false
}
prev = node
rightRes := travel(node.Right)
return leftRes && rightRes
}
return travel(root)
}
```
## JavaScript
辅助数组解决
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isValidBST = function (root) {
let arr = [];
const buildArr = (root) => {
if (root) {
buildArr(root.left);
arr.push(root.val);
buildArr(root.right);
}
}
buildArr(root);
for (let i = 1; i < arr.length; ++i) {
if (arr[i] <= arr[i - 1])
return false;
}
return true;
};
```
递归中解决
```javascript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
let pre = null;
var isValidBST = function (root) {
let pre = null;
const inOrder = (root) => {
if (root === null)
return true;
let left = inOrder(root.left);
if (pre !== null && pre.val >= root.val)
return false;
pre = root;
let right = inOrder(root.right);
return left && right;
}
return inOrder(root);
};
```
-----------------------
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码一.jpg width=500> </img></div>