欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

## 98.验证二叉搜索树 题目地址:https://leetcode-cn.com/problems/validate-binary-search-tree/ 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 假设一个二叉搜索树具有如下特征: * 节点的左子树只包含小于当前节点的数。 * 节点的右子树只包含大于当前节点的数。 * 所有左子树和右子树自身必须也是二叉搜索树。 ![98.验证二叉搜索树](https://img-blog.csdnimg.cn/20210203144334501.png) ## 思路 要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。 有了这个特性,**验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。** ## 递归法 可以递归中序遍历将二叉搜索树转变成一个数组,代码如下: ``` vector 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 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 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)中我们分明写出了痛哭流涕的简洁迭代法,怎么在这里不行了呢,因为本题是要验证二叉搜索树啊。 ## 总结 这道题目是一个简单题,但对于没接触过的同学还是有难度的。 所以初学者刚开始学习算法的时候,看到简单题目没有思路很正常,千万别怀疑自己智商,学习过程都是这样的,大家智商都差不多,哈哈。 只要把基本类型的题目都做过,总结过之后,思路自然就开阔了。 ## 其他语言版本 Java: Python: Go: ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)