98. 验证二叉搜索树

98. 验证二叉搜索树

98. 验证二叉搜索树

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

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

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

示例 1:
在这里插入图片描述

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

示例 2:
在这里插入图片描述

输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4

提示:

  • 树中节点数目范围在[1, 10^4] 内
  • -2^31 <= Node.val <= 2^31 - 1

思路

要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。

有了这个特性,验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。

递归法

可以递归中序遍历将二叉搜索树转变成一个切片,代码如下:

func dfs(root *TreeNode,arr *[]int) {
    if root == nil {
        return
    }

    dfs(root.Left,arr) // 左
    *arr = append(*arr,root.Val) // 中
    dfs(root.Right,arr) // 右
}

然后只要比较一下,这个切片是否是有序的,注意二叉搜索树中不能有重复元素。

// 数组严格递增
for i := 1;i < len(arr);i++ {
    // 注意要小于等于,搜索树里不能有相同元素
    if arr[i] <= arr[i - 1] {
        return false
    }
}
return true

整体Go代码如下:

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func isValidBST(root *TreeNode) bool {
    /*要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。
    可以递归中序遍历将二叉搜索树转变成一个数组
    然后只要比较一下,这个数组是否是有序的,注意二叉搜索树中不能有重复元素。
    */
    if root == nil {
        return true
    }
    arr := make([]int,0)
    dfs(root,&arr)
    // 数组严格递增
    for i := 1;i < len(arr);i++ {
        if arr[i] <= arr[i - 1] {
            return false
        }
    }
    return true
}

func dfs(root *TreeNode,arr *[]int) {
    if root == nil {
        return
    }

    dfs(root.Left,arr) // 左
    *arr = append(*arr,root.Val) // 中
    dfs(root.Right,arr) // 右
}

以上代码中,我们把二叉树转变为切片来判断,是最直观的,但其实不用转变成数组,可以在递归遍历的过程中直接判断是否有序。

这道题目比较容易陷入两个陷阱:

陷阱1

不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了。

写出了类似这样的代码:

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func isValidBST(root *TreeNode) bool {
    if root == nil {
        return true
    }
    if root.Left != nil && root.Val <= root.Left.Val {
        return false
    }
    if root.Right != nil && root.Val >= root.Right.Val {
        return false
    }
    return isValidBST(root.Left) && isValidBST(root.Right)
}

我们要比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点。所以以上代码的判断逻辑是错误的。

例如: [10,5,15,null,null,6,20] 这个case
在这里插入图片描述
节点10大于左节点5,小于右节点15,但右子树里出现了一个6 这就不符合了!

即上面每个节点都符合大于左节点,小于右节点,但是不符合每个节点大于左子树中所有节点,小于右子树中所有节点

陷阱2

样例中最小节点 可能是int的最小值,如果这样使用最小的int来比较也是不行的。

了解这些陷阱之后我们来看一下代码应该怎么写:

递归三部曲:

  1. 确定递归函数,返回值以及参数
    我们需要将左子树和右子树的取值范围限制住,所以需要两个参数min和max,而题目所给的数值可能就是int值的边界,我们进行+1-1操作会越界,所以定义为int64类型
func dfs(root *TreeNode,min,max int64) bool {}

我们是在寻找一个不符合条件的节点,如果没有找到这个节点就遍历了整个树,如果找到不符合的节点了,则可以立刻返回。

代码如下:

if int64(root.Val) < min || int64(root.Val) > max {
  	return false
 }
  1. 确定终止条件
    如果是空节点 是不是二叉搜索树呢?
    是的,二叉搜索树也可以为空!

代码如下:

if root == nil {
        return true
    }
  1. 确定单层递归的逻辑
    限定左右子树的范围,递归左右子树。
//由于root.Val的值可能开始就是int的边界,故-1和+1可能会越界,因此转成int64型
 left := dfs(root.Left,min,int64(root.Val) - 1) // 左子树取数范围
 right := dfs(root.Right,int64(root.Val) + 1,max) // 右子树取数范围
 return left && right

Go整体代码如下:

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func isValidBST(root *TreeNode) bool {
    /*乍一看,这是一个平凡的问题。只需要遍历整棵树,检查 node.right.val > node.val 和
    node.left.val < node.val 对每个结点是否成立。
    问题是,这种方法并不总是正确。不仅右子结点要大于该节点,整个右子树的元素都应该大于该节点
    这意味着我们需要在遍历树的同时保留结点的上界与下界,判断该结点的值是否在范围内。*/
    if root == nil {
        return true
    }
    
    //最初根节点的值在int范围内即可
    return dfs(root,math.MinInt64,math.MaxInt64)

}

func dfs(root *TreeNode,min,max int64) bool {
    if root == nil {
        return true
    }
    if int64(root.Val) < min || int64(root.Val) > max {
        return false
    }
    
    //由于root.Val的值可以一开始就是int的边界,故-1和+1可能会越界,因此转成int64型
    left := dfs(root.Left,min,int64(root.Val) - 1) // 左子树取数范围
    right := dfs(root.Right,int64(root.Val) + 1,max) // 右子树取数范围
    return left && right
}

在这里插入图片描述
如果题目给的就是int64类型,则应该用左闭右闭,避免-1+1操作,如下

func isValidBST(root *TreeNode) bool {
	// 二叉搜索树也可以是空树
    if root == nil {
        return true
    }
    // 由题目中的数据限制可以得出min和max
    return check(root,math.MinInt64,math.MaxInt64)
}

func check(node *TreeNode,min,max int64) bool {
    if node == nil {
        return true
    }

    if min >= int64(node.Val) || max <= int64(node.Val) {
        return false
    }
    // 分别对左子树和右子树递归判断,如果左子树和右子树都符合则返回true
    return check(node.Right,int64(node.Val),max) && check(node.Left,min,int64(node.Val))
}

迭代法

迭代法中序遍历稍加改动就可以了,Go代码如下:

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func isValidBST(root *TreeNode) bool {
    
    if root == nil {
        return true
    }
    curTreeNode := root
    var preTreeNode *TreeNode // 记录前一个节点
    stack := make([]*TreeNode,0)

    for curTreeNode != nil || len(stack) != 0 {
        if curTreeNode != nil {
            stack = append(stack,curTreeNode)
            curTreeNode = curTreeNode.Left  // 左
        } else {
            curTreeNode = stack[len(stack)-1]  // 中
            stack = stack[0:len(stack) - 1]
            // 二叉搜索树的中序遍历会严格递增,不符合时可以直接返回false
            if preTreeNode != nil && curTreeNode.Val <= preTreeNode.Val {
                return false
            }
            preTreeNode = curTreeNode // 记录请一个访问的节点
            curTreeNode = curTreeNode.Right // 右
        }
    }

    return true

}

总结

这道题目是一个简单题,但对于没接触过的同学还是有难度的。

所以初学者刚开始学习算法的时候,看到简单题目没有思路很正常,千万别怀疑自己智商,学习过程都是这样的,大家智商都差不多。

只要把基本类型的题目都做过,总结过之后,思路自然就开阔了,加油💪

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值