持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情
验证二叉搜索树
题目
98. 验证二叉搜索树
难度中等
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入: root = [2,1,3]
输出: true
示例 2:
输入: root = [5,1,4,null,null,3,6]
输出: false
解释: 根节点的值是 5 ,但是右子节点的值是 4 。
中序遍历
好久没看过树的题目了,数据结构算法课学的东西都忘干了。先来个中序遍历的代码熟悉一下
//这是树的类,二叉树,包含节点值,左节点,右节点.也就是左右也是一棵树
//如果没有树也要设为null,这有利于树的使用.
class TreeNode {
constructor(val, left, right) {
this.val = (val === undefined ? 0 : val);
this.left = (left === undefined ? null : left);
this.right = (right === undefined ? null : right);
}
}
var inorderTraversal = function (root) {
// 中序遍历是什么意思? 给你一个树 你按照中序遍历的方式返回结果
//这里用数组存储结果。即左->中->右,
//相对的还有先根,后根遍历,即先遍历跟节点或者最后遍历树的根节点。
let ans = []
const order = (root) => {
if (!root) {
//null的判断结果是false
return
//java还可以返回null,那么就是返回构造的数组了。
}
order(root.left)
//递归的往左走,直至没有节点
ans.push(root.val)
//当左边没有节点那么这个节点入栈
order(root.right);
//再往右走
}
order(root)
return ans;
};
代码写起来简单,主要还是理解复习树的概念.
解法一 先根遍历
我们再来看本题,首先就是二叉搜索树(我一开始还没想起是什么树),它还有一个名字,就是二叉排序树,也就是根节点的值永远大于左子树的maxValue,小于右子树的minValue,依次往下,形成左中右的排序的二叉树.
而我们需要做的就是通过代码验证一棵二叉树是不是二叉排序树.那么到这里其实就很简单了,首先将排序二叉树的概念反转一下:左子树小于父节点,右子树大于父节点,我们通过先跟遍历,将根节点与max/min比较即可.
来看代码:
const isValidBST = function (root) {
return doVal(root, -Infinity, Infinity)
//因为需要max与min,所有用do函数带max与min的参数来进行递归.
};
const doVal = (root, min, max) => {
if (root === null) {
return true
}
if (root.val > max || root.val < min) {
//很简单,比max小,min大即可
return false
}
return doVal(root.left, min, root.val) && doVal(root.right, root.val, max);
//这里是重点,往下递归,我们的max和min是在不断变化的,这里看下面细说
}
考虑下面这种情况,我们比较secondRoot<root,secondRoot<10,都正确,但是root<10,这违背了根节点大于左子树这一性质.因此我们不能简单的将大小比较限于相连节点的比较,就比如这里10<max,这个max应该是secondRoot的父节点才对.
也就是说当往下走到下一棵左子树的时候,我们需要将max改为当前根节点的值,min不变,同理走到右子树的时候,min应该变为当前节点的值,max不变.具体体现就是最后return返回的语句.
而递归的结果需要左右子树都满足排序二叉树的性质,因此我们先&&再return.
解法二 中序遍历
由上面的中序递归遍历的代码,假如我们使用中序遍历,那么需要比较什么?
既然是 左->中->右 的顺序,那么简单的两次比较左<中, 中<右, 还是上面那个问题,怎么保证节点值大于左子树,小于右子树?即大于左子树最大值,小于右子树最小值,
解决这个问题,先来看中序遍历的另一种方法,即使用栈模拟递归
const inorderTraversalStack = (root) => {
const ans = [];
const stack = [];
while (root || stack.length) {
while (root) {
stack.push(root);
root = root.left;
}
// 一直往左走,直到没有节点,用栈结构存储遍历的节点
root = stack.pop();
// 向左结束之后我们再回退一个节点,即父节点
ans.push(root.val);
// 此时将节点值存入结果数组
root = root.right;
// 再向右走
}
return ans;
};
这也很简单,就是用栈存储当前遍历到的节点,保证继续往左走,到头了将指针指向出栈的节点p,再向右走,假如p.right右走完了,此时再出栈的话就是p的父节点.以此来进行中序遍历,也是左->中->右.
再来看,既然要是二叉排序树,那么中根遍历成功二叉树的一定就是二叉排序树,因为左中右的顺序刚好就是左<中<右.因此我们在做保存节点的操作的同时,做比较大小的操作和将maxVal变量值改变就可以了!
代码如下:
const isValidBST = (root) => {
const stack = [];
let p = root;
let maxCur = -Infinity;
while (stack.length || p !== null) {
while (p !== null) {
stack.push(p)
p = p.left;
}
p = stack.pop();
if (p.val <= maxCur) {
//判断当前节点值需要大于max
return false;
}
maxCur = p.val;
//改变最大值
p = p.right;
}
return true;
//没问题就返回true
}
总结
本次文章的难点在于树的概念,遍历的方法,二叉排序树的性质问题. 其实在掌握栈模拟递归中序遍历以后,会发现用这种方法来完成本题思路很简单,代码也很简单!
结语
本次的文章到这里就结束啦!♥♥♥读者大大们认为写的不错的话点个赞再走哦 ♥♥♥
每天一个知识点,每天都在进步!♥♥