掘金团队号上线,助你 Offer 临门! 点击 查看详情
验证二叉搜索树(题号98)
题目
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
链接
解释
这就涉及到了二叉搜索树的基本定义了,定义很简单,所有左子树的值一定比当前节点的值小,同时所有右子树的值一定比当前节点的值大。注意,这里的定义是所有的值,并不局限在某一层。
一开始毫无头绪,这是第一次遇到有左右的情况,对于如何遍历没有什么好的想法,后续才发现原来迭代的方法十分简单,但是遍历好像只有中序遍历的答案,这让我有点小惊讶,还以为会有普通遍历的解法,可是看了许久也么有找到,有点奇怪。
自己的答案(递归)
👇是笔者解题心路历程。
首先,笔者很快就写出来一个答案,利用的就是二叉搜索树的特点,左节点的值会小于当前节点,右结点的值会大于当前节点,然后一波遍历:
var isValidBST = function(root) {
if (!root) return true
if (root.left && root.left.val > root.val) {
return false
}
if (root.right && root.right.val < root.val) {
return false
}
return isValidBST(root.left) && isValidBST(root.right)
};
看起来非常简单,主要就是判断左右节点的值和当前节点值的关系,然后一提交,原地GG。
这里的问题很简单,因为二叉搜索树的大小关系并不局限于某一个层级,它们的大小关系是存在于整个树上的,也就是顶级节点的值一定会大于所有左子节点的值,并且小于所有右子节点的值。
这是isValidBST
方法就需要多两个参数了,用来传递给左子节点和右子节点,用来维持整个树的大小关系。并且每个层级都会变化,所以需要动态赋值。
这里的想法也是比较简单的,如果是左子树,那么它的节点值必然需要大于一个最小值,这个最小值其实就是当前节点的值,右子树同理,于时就得出了这样的一个答案👇:
var isValidBST = function(root, max = Number.MAX_SAFE_INTEGER, min = Number.MIN_SAFE_INTEGER) {
if (!root) return true
if (root.left && (root.left.val >= root.val || root.left.val <= min)) {
return false
}
if (root.right && (root.right.val <= root.val || root.right.val >= max)) {
return false
}
return isValidBST(root.left, root.val, min)
&& isValidBST(root.right, max, root.val)
};
然后再一跑,果然就成功了,但这其实并不是最好的递归解法。
更好的方法(递归)
var isValidBST = function(root) {
function checkNode(node, max, min) {
if (!node) return true
if (node.val <= min || node.val >= max) return false
return checkNode(node.left, node.val, min) && checkNode(node.right, max, node.val)
}
return checkNode(root, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER)
};
在笔者自己的答案中,判断的是当前节点左子树的值和右子树的值,其实这样有点复杂了。
这里只需要判断当前节点值和max
、min
的关系即可,当前节点的值必然要大于最小值,小于最大值,否则就无法维持整个树的大小关系。
max
和min
的赋值也很简单,初始化很简单,因为是顶级节点,所以没有限制,只需要传入JavaScript
所接受的最大值和最小值即可。
之后每次遍历时,如果是左子树,那么最大值就是当前节点的值,最小值不用管,继续使用JavaScript
中的最小数字即可;如果是右子树,那么最大值不用管,最小值就是当前节点的值。
想写出这个答案需要对二叉搜索树的理解十分透彻才可以,否则只能写出像笔者一样的答案,虽然写出来了,但依然有点冗余,理解得不够透彻。
PS:这里还可以将代码浓缩为一行。
var isValidBST = function(root, max = Number.MAX_SAFE_INTEGER, min = Number.MIN_SAFE_INTEGER) {
return !root || root.val < max && root.val > min && isValidBST(root.left, root.val, min) && isValidBST(root.right, max, root.val)
};
更好的方法(迭代)
var isValidBST = function (root) {
var stack = []
inorder = Number.MIN_SAFE_INTEGER
while (stack.length || root) {
while (root) {
stack.push(root)
root = root.left
}
root = stack.pop()
if (root.val <= inorder) return false
inorder = root.val
root = root.right
}
return true
};
代码不长,但理解起来确实有点难度。先定义两个变量stack
和inorder
,stack
用来存放节点值,inorder
用来存放当前的数据。
这里要说到中序遍历的一个特点,那就是遍历的结果必然是从小到大,如果有一个值比前一个小,那就证明这个数不是二叉搜索树,具体逻辑可看下面一个GIF。
回归正题,确定两个变量后就开始遍历了,首先确定stack
并不是空数组,并且root
不为null
,否则代表遍历已经结束了。之后遍历root
,将root
拆开,拿到其所有的左节点,存放到stack
中。
之后再从stack
中拿出存储的节点信息,判断当前节点的val
是不是大于inorder
,如果不是直接返回false
,否则将val
赋值给inorder
,再拿到右节点,开始新一轮的遍历。
如此,遍历完成后就可以得出结果。
这里真的是很难理解,反正笔者是想了得了半个多小时才理解,可能是比较愚钝吧,好好想想,总是可以理解的。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇
有兴趣的也可以看看我的个人主页👇