前端刷题路-Day9|刷题打卡

597 阅读3分钟

掘金团队号上线,助你 Offer 临门! 点击 查看详情

验证二叉搜索树(题号98)

题目

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:
    2
   / \
  1   3
输出: true

示例 2:

输入:
    5
   / \
  1   4
     / \
    3   6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
     根节点的值为 5 ,但是其右子节点值为 4

链接

leetcode-cn.com/problems/va…

解释

这就涉及到了二叉搜索树的基本定义了,定义很简单,所有左子树的值一定比当前节点的值小,同时所有右子树的值一定比当前节点的值大。注意,这里的定义是所有的值,并不局限在某一层。

一开始毫无头绪,这是第一次遇到有左右的情况,对于如何遍历没有什么好的想法,后续才发现原来迭代的方法十分简单,但是遍历好像只有中序遍历的答案,这让我有点小惊讶,还以为会有普通遍历的解法,可是看了许久也么有找到,有点奇怪。

自己的答案(递归)

👇是笔者解题心路历程。

首先,笔者很快就写出来一个答案,利用的就是二叉搜索树的特点,左节点的值会小于当前节点,右结点的值会大于当前节点,然后一波遍历:

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)
};

在笔者自己的答案中,判断的是当前节点左子树的值和右子树的值,其实这样有点复杂了。

这里只需要判断当前节点值和maxmin的关系即可,当前节点的值必然要大于最小值,小于最大值,否则就无法维持整个树的大小关系。

maxmin的赋值也很简单,初始化很简单,因为是顶级节点,所以没有限制,只需要传入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
};

代码不长,但理解起来确实有点难度。先定义两个变量stackinorderstack用来存放节点值,inorder用来存放当前的数据。

这里要说到中序遍历的一个特点,那就是遍历的结果必然是从小到大,如果有一个值比前一个小,那就证明这个数不是二叉搜索树,具体逻辑可看下面一个GIF。

fig1

回归正题,确定两个变量后就开始遍历了,首先确定stack并不是空数组,并且root不为null,否则代表遍历已经结束了。之后遍历root,将root拆开,拿到其所有的左节点,存放到stack中。

之后再从stack中拿出存储的节点信息,判断当前节点的val是不是大于inorder,如果不是直接返回false,否则将val赋值给inorder,再拿到右节点,开始新一轮的遍历。

如此,遍历完成后就可以得出结果。

这里真的是很难理解,反正笔者是想了得了半个多小时才理解,可能是比较愚钝吧,好好想想,总是可以理解的。



PS:想查看往期文章和题目可以点击下面的链接:

这里是按照日期分类的👇

前端刷题路-目录(日期分类)

经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇

前端刷题路-目录(题型分类)

有兴趣的也可以看看我的个人主页👇

Here is RZ