题目描述
解法一:递归
值得注意的是,对于一棵二叉搜索树中的每个节点,不仅右子结点要大于该节点,其整个右子树的节点都应该大于该节点。同理,每个节点不仅左子结点要小于该节点,其整个左子树的节点都应该小于该节点。
因此,在遍历树的同时,需要保留每个节点的上界与下界,在比较时,不仅与子结点的值进行比较,也要与上下界比较。这里的上下界指的是,对于每个节点,其左子树的所有节点都有一个上界,都应该小于该节点;同理,其右子树所有节点的下界即为该节点的值。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def isValidBST(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
def helper(node, lower = float('-inf'), upper = float('inf')): # 对于根节点,上下界初始化为无穷大和无穷小
if not node: # 树为空也满足二叉搜索树
return True
if node.val <= lower or node.val >= upper: # 对于每个节点,判断是否介于上下界之间
return False
if not helper(node.right, node.val, upper): # 递归遍历该节点的右子树,右子树中所有节点的下界为该节点的值
return False
if not helper(node.left, lower, node.val): # 递归遍历该节点的左子树,左子树中所有节点的上界为该节点的值
return False
return True
return helper(root)
- 时间复杂度 : O(N)。每个节点有且仅访问一次。
- 空间复杂度 : O(N)。递归过程需要压栈处理,整棵树从根节点开始,每个节点都压入递归调用栈中判断了一遍。
解法二:迭代法
使用广度优先搜索,从根节点一直按层遍历到其左右子树的叶子节点,判断是否都满足条件。
class Solution(object):
def isValidBST(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
if root == None: # 树为空也满足二叉搜索树
return True
stack = [(root, float('-inf'), float('inf'))] # 栈里头存的是元组(节点,节点的下界,节点的上界),初始只压入根节点,其上下界初始化为无穷大和无穷小
while stack:
# 每次从栈中弹出一个节点进行判断,若满足条件,就继续把其左右子节点压栈,
# 下次循环又弹出其子节点进行判断,满足条件则继续压入其子节点的左右子节点
# 每个节点都入栈又出栈进行判断,直到栈为空,即遍历完整棵树
root, lower, upper = stack.pop() # 弹出栈顶的元组
if not root: # 遍历整棵树的过程中,遇到空节点就直接跳过
continue
val = root.val
if val <= lower or val >= upper: # 判断当前节点是否满足条件
return False
stack.append((root.left, lower, val)) # 将当前节点的左子节点压栈,其上界变成当前节点的值
stack.append((root.right, val, upper)) # 将当前节点的右子节点压栈,其下界变成当前节点的值
return True # 每个节点都满足条件则返回 True
- 时间复杂度 : O(N)。每个节点访问一次。
- 空间复杂度 : O(N)。辅助栈空间大小,每个节点都入栈一次,之后出栈进行判断。
解法三:中序遍历
二叉搜索树一个很重要的特性就是:对二叉搜索树进行中序遍历,可得到一个递增的序列。因此,检查一个树是否是二叉搜索树可以使用中序遍历。
class Solution(object):
def isValidBST(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
def inorder(root):
if root == None:
return []
else:
return inorder(root.left) + [root.val] + inorder(root.right)
inorder_list = inorder(root) # 中序遍历得到的序列
return inorder_list == sorted(set(inorder_list)) # 判断中序遍历结果是否满足递增排序。
# 内建函数 sorted方法返回的是一个新的 list,所以sorted(set(inorder_list))返回类型是 list
事实上不需要保留整个 inorder 列表,我们只需要在中序遍历过程中,每次记住上一个遍历过的节点,然后判断当前遍历到的节点的值是否大于上一个节点的值即可。
下面的代码使用了栈来存放节点,从而避免了使用递归进行中序遍历。整个过程就是从最左边的最下面一个节点开始,不停与前一个遍历过的节点比较值,往后遍历当前节点的父节点或者右子节点,重复之前的比较过程,直到栈尽点绝。
class Solution(object):
def isValidBST(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
#---中序遍历(左子树 -> 结点 -> 右子树)---
stack = []
prev = float('-inf') # 上一个元素值初始化为无穷小
node = root # node初始化为根节点
while stack or node:
while node: # 沿着节点的左子树的最左边一直往深层遍历,直到左子树中第一个无左子节点的节点
stack.append(node) # 把最左边的一排左子节点依次压入栈
node = node.left
node = stack.pop() # 第一个出栈的节点即为中序遍历的第一个元素,此后出栈顺序即为中序遍历的排列顺序
if node.val <= prev: # 对于二叉搜索树而言,中序遍历结果中每个元素都应该比上一个元素大
return False
prev = node.val # 上一个元素值更新为当前出栈节点的值
node = node.right # 将当前 node的右子节点放入下次循环
# 1.如果没有右子节点,下次循环从栈中弹出的是当前 node的父节点,将该节点与 pre比较大小;
# 2.如果有右子节点,则压入栈,如果该右子节点还有左子节点,继续压栈,重复上面的判断过程。
return True
- 时间复杂度 : 最坏情况下(树为二叉搜索树或破坏条件的元素是最右叶子结点)为 O(N)。
- 空间复杂度 : O(N),用于存储 stack。
最后,如果大家有更好的Python解法,欢迎在评论区分享下您的解法,一起进步,感谢^ o ^~