【Python实战】LeetCode98:验证二叉搜索树

题目描述

【Python实战】LeetCode98:验证二叉搜索树_第1张图片

解法一:递归

值得注意的是,对于一棵二叉搜索树中的每个节点,不仅右子结点要大于该节点,其整个右子树的节点都应该大于该节点。同理,每个节点不仅左子结点要小于该节点,其整个左子树的节点都应该小于该节点。

因此,在遍历树的同时,需要保留每个节点的上界与下界,在比较时,不仅与子结点的值进行比较,也要与上下界比较。这里的上下界指的是,对于每个节点,其左子树的所有节点都有一个上界,都应该小于该节点;同理,其右子树所有节点的下界即为该节点的值。

# 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 ^~

你可能感兴趣的:(Python学习笔记)