今天是LeetCode专题第63篇文章,我们一起来聊聊LeetCode中的第98题,二叉搜索树的合法性判断问题。和之前介绍过的几道题类似,也是一道关于二叉搜索树的问题。
本题的官方难度是Medium,点赞4167,反对552,差评率比之前的两道题稍稍高了一点点,称得上是一道多半好评的题目。通过率27.7%相比之前40%以上的通过率,这道题的通过率算是Medium难度的题目中偏低的,也就意味着这道题相对困难一些,但题意还是很正的,和之前一样没有奇怪的逻辑,考察的就是朴实的理解。
题意
题意很简单,给定一棵二叉树要求判断它是否是一棵合法的二叉搜索树(BST)。
一棵合法的二叉搜索树需要满足三个条件:
- 左子树中的所有节点小于根节点
- 右子树中的所有节点大于根节点
- 不存在两个节点的值相等
样例
2
/ \
1 3
Input: [2,1,3]
Output: true
复制
5
/ \
1 4
/ \
3 6
Input: [5,1,4,null,null,3,6]
Output: false
Explanation: The root node's value is 5 but its right child's value is 4.
复制
题意
我们第一反应就是通过递归来解这道题,递归的思路当然是可以的,我们把判断的逻辑一层层嵌套,通过递归的形式让它自己调用自己,从而大大简化代码的编写。但是这其中有一个问题,就是这道题当中的逻辑关系相对不太容易传递。
我们举一个很简单的例子:
这是一颗二叉树,为了简化,我们把左子树简化成了A,右子树简化成了B。对于A来说,我们需要保证A当中的所有元素都小于root,然而对于B来说,它需要保证其中所有的元素都大于root。如果我们希望递归来实现这个判断的话,我们需要通过递归来遍历A和B当中的所有元素,来一一判断是否是满足条件的。
这当然是可行的,但是有一个很大的问题是效率很低。因为对于root来说我们需要遍历一次A和B,对于A和B当的根节点来说,我们又需要遍历一次。这其中有很多的逻辑和计算是重复的,这会带来大量的浪费,我们要做的是优化算法的流程,让它变得更优。
其实也很好分析,虽然二叉搜索树限制了左子树和右子树上所有的元素,但是我们仔细想想,其实不需要每个元素都考虑,只需要关注左子树当中最大值和右子树当中最小值就行了。如果左子树当中的最大值也小于根节点,右子树当中的最小值也大于根节点,那么就一定是合法的。反过来如果左子树存在比根节点大的数或者是右子树存在比根小的数就都是非法的。
我们可以通过递归来同时维护当前子树当中的最大值和最小值,只要用左子树的最大值和右子树的最小值和根节点进行校验即可。
附上代码:
import sys
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
def dfs(node):
if node is None:
return True, -sys.maxsize, sys.maxsize
# 递归获取左右子树的最大值以及最小值以及是否合法
left, lmax, lmin = dfs(node.left)
right, rmax, rmin = dfs(node.right)
# 如果左右子树存在非法,直接返回
if (not left) or (not right):
return False, lmax, rmin
# 否则比较左子树最大值与右子树最小值
if lmax >= node.val or rmin <= node.val:
return False, lmax, rmin
return True, max(node.val, rmax), min(node.val, lmin)
return dfs(root)[0]
复制
这段代码看起来不长,但是其实不太好写,尤其是由于Python支持多值返回,所以一些操作和C++的递归不太一致,对于C++选手来说可能不是非常友好。但核心的原理是我们在递归求子树的最大值和最小值的同时也判断了子树是否是一棵合法的子树,递归不难写但要把这两个逻辑整合在一起对新手来说可能不太容易,推荐大家最好自己亲手写一次,加深一下理解。
- END -