js算法题解(第二十三天)--98. 验证二叉搜索树

240 阅读2分钟

「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战

前言

为什么我们要使用二叉搜索树呢?

回答这个问题我们先说一下二叉树的由来

在链表中,插入、删除速度很快,但查找速度较慢。

在数组中,查找速度很快,但插入删除速度很慢。

image.png

为了解决这个问题,找寻一种能够在插入、删除、查找、遍历等操作都相对快的容器,于是人们发明了二叉树

值得注意的是,无特征的二叉树在工业上是没啥用处的,一般都是用的bst、avl等具有特殊特征的二叉树。

比如在bst中,中序遍历可以得到顺序输出,插入查找删除的速度都相当快速(logn).

这边说的bst,就是二叉搜索树,既然他这么快速,那我们肯定要学呀

那么我们首先要先验证是不是一个搜索二叉树,来吧,真题来了

题目

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

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

示例 1:

输入:root = [2,1,3] 输出:true

image.png

分析

第一步:从题目中提取关键字

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

也就是说左<根<右,并且左子树和右子树也都要满足左<根<右

这道题其实主要从二叉搜索树的定义来去做

我们先来回顾一下二叉搜索树的定义

  • 它可以是一棵空树
  • 它可以是一棵由根结点、左子树、右子树组成的树,同时左子树和右子树都是二叉搜索树,且左子树上所有结点的数据域都小于等于根结点的数据域,右子树上所有结点的数据域都大于等于根结点的数据域

只有符合以上两种情况之一的二叉树,可以称之为二叉搜索树。

所以我们要检验对非空的树中的左右子树进行遍历,看是否满足左<根<右

我们先初始化遍历函数,并设置初始值为极小和极大

var isValidBST = function(root) {
    const dfs = function(minValue,root,maxValue){
    }
     // 遍历是否满足左<根<右
     // 为什么我们不直接dfs(root.left,root,root.right),因为要判断的太多,而极小和极大是绝对满足,所以我们初始化用极小和极大,然后在不断变化左子树和右子树
    return dfs(-Infinity,root,Infinity)
};

遍历的时候就把判断是否是二叉树的条件写上,写上什么时候是true,什么时候是false

那么答案也就出来了

题解

var isValidBST = function(root) {
    const dfs=function(minValue,root,maxValue){
        if(!root){
            return true;
        }
        if(root.val<=minValue||root.val>=maxValue){
            return false;
        }
        return dfs(minValue,root.left,root.val)&&dfs(root.val,root.right,maxValue);
    }
    // 遍历是否满足左<中<右
    return dfs(-Infinity,root,Infinity)
};

但是我觉得吧,面试的时候这种方法并不好写出来,因为这种方法并不好和我们已知的一些二叉树遍历方法联系起来,所以我们要往已知的方法上想

搜索二叉树的特点是什么?

中序遍历后,从前到后的数字都是有序的,这句话真实醍醐灌顶呀,我们只需要一次中序遍历,然后每次比对当前的值是否大于前面的值,如果遍历结束后,都满足的话,那就是搜索二叉树

首先我们先在函数中在写一个遍历函数

var isValidBST = function(root) {

    const travers = function(root){
        // 判断空值二叉搜索树
        // 如果是空树的,也是二叉搜索树,返回true
        if(!root){
            return true;
        }
        // 判断有值二叉搜索树
        travers(root.left)        
        console.log(root.val);
        travers(root.right)
    }
    travers(root);
};

然后初始化pre也就是前一个数字,和flag也就是要返回的结果

var isValidBST = function(root) {
    // 初始化前一个数为最小值
    let pre = -Infinity;
    let flag = true;
    const travers = function(root){
        // 判断空值二叉搜索树
        // 如果是空树的,也是二叉搜索树,返回true
        if(!root){
            return true;
        }
        // 判断有值二叉搜索树
        travers(root.left)        
        console.log(root.val);
        travers(root.right)
    }
    travers(root);
    return flag;
};

如果pre大于等于当前值得话,那么flag为false; 然后我们在每次打印的时候改变pre的值,那么答案也就出来了

题解

var isValidBST = function(root) {
    // 初始化前一个数为最小值
    let pre = -Infinity;
    let flag = true;
    const travers = function(root){
        // 判断空值二叉搜索树
        // 如果是空树的,也是二叉搜索树,返回true
        if(!root){
            return true;
        }
        // 判断有值二叉搜索树
        travers(root.left)        
        if(pre>=root.val){
            flag = false;
        }
        pre = root.val;
        travers(root.right)
    }
    travers(root);
    return flag;
};

总结

我们做题的时候一定要多问为什么,既要知其然,又要知其所以然,否则你面试的时候,说不一定哪一步你没理解,就忘了,结果功亏一篑。所以不要有侥幸心理。

参考