Leetcode刷题笔记之: 二叉树

本文参考leetcode的数据结构与算法
笔记系列github地址

目录

    • 1.深度遍历
      • 1.1 前序遍历
      • 1.2 中序遍历
      • 1.3 后序遍历
    • 2. 广度遍历
    • 3.利用递归解决的问题

# 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

1.深度遍历

三种深度遍历方式的递归与迭代方式都需要掌握

1.1 前序遍历

前序遍历的读取顺序为 中左右,即先读取根节点,再读取左子树,最后读取右子树

对于前序遍历的递归实现很简单,仅仅只需要按照中左右的顺序将递归结果拼接起来即可,注意root为空的情况,此时应该输出空值

# 前序遍历递归
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        result = []
        # 如果不是结点,直接返回空列表
        if root:
            # 如果是结点,先读根节点
            result.append(root.val)
            # 再读左子树
            if root.left:
                result += self.preorderTraversal(root.left)
            # 再读右子树
            if root.right:
                result += self.preorderTraversal(root.right)
        return result

对于迭代的实现,需要借助栈(stack)来实现。栈是一种先进后出的结构,具体在python中的实现便是对list的append()操作(在尾部添加)和pop()操作(弹出尾部元素)。

首先维护一个result列表,一个栈,初始元素是根节点。不断弹出栈的尾部元素,将其作为结果输出。然后将这个弹出的元素的右节点和左节点分别放入栈中。为什么这么设置?因为栈是先入后出,所以这样便可以保证下一次循环首先弹出的是左节点(子树),左边读完然后才是右子树,以此实现前序遍历的”中左右“(第一个弹出的就是根节点)

# 前序遍历迭代
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        result = []
        stack = [root]
        # 当stack不为空
        while stack:
            # 弹出尾部元素
            cur = stack.pop()
            # 尾部元素输出
            result.append(cur.val)
            # 按照先右再左的原则将当前元素的子节点压入栈
            if cur.right:
                stack.append(cur.right)
            if cur.left:
                stack.append(cur.left)
        return result

1.2 中序遍历

中序遍历的读取顺序为 左中右,即先读取左子树,再读取根节点,最后读取右子树

同样的道理,对于递归的解法只需要将结果拼接就好

# 中序遍历递归
class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        # 递归法
        if not root:
            return []
        return self.inorderTraversal(root.left) + [root.val] +self.inorderTraversal(root.right)

中序遍历的迭代解法跟前序有些不同。中序遍历需要用到指针。
首先指针指向根节点,从根节点到最左边叶结点路径上所有的点填充进栈。
然后弹出栈的尾部元素(首先是最左侧叶结点),然后输出
弹出后指针指向弹出结点的右节点。

# 中序遍历迭代
class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        # 迭代法
        if not root:
            return []
        result= []
        stack = []
        cur = root
        # 当stack清空,root也读完后停止
        while stack or cur:
            # 填充stack直至叶节点
            while cur:
                stack.append(cur)
                cur = cur.left
            # stack顶层pop出为输出结果
            cur = stack.pop()
            result.append(cur.val)
            # 进行右子树
            cur = cur.right
        return result

1.3 后序遍历

后序遍历的读取顺序为 左右中,即先读取左子树,再读取右子树,最后读取根节点

# 后序遍历递归
class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        return self.postorderTraversal(root.left) + self.postorderTraversal(root.right) + [root.val]

后序遍历其实写法上跟前序遍历几乎一样。
同样首先维护一个result列表,一个栈,初始元素是根节点。不断弹出栈的尾部元素,将其作为结果输出。然后将这个弹出的元素的左节点和右节点分别放入栈(跟前序正好相反),此时便可以注意到输出的结果顺序(中右左),最后输出是将结果反向便可以到”左右中“的输出

# 后序遍历迭代
class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        result = []
        stack = [root]

        while stack:
            cur = stack.pop()
            if cur.left:
                stack.append(cur.left)
            if cur.right:
                stack.append(cur.right)
            result.append(cur.val)
        return result[::-1]

2. 广度遍历

# 层次遍历
class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        if not root:
            return []
        result = []
        queue = [root]
        while queue:
            # 构建某一层的暂时result和queue
            # 不选择利用append的原因是其复杂度是O(n),若节点过多则很慢
            # 而重新构建新queue会更快,因为最多只有一层的结点
            temp_res = []
            temp_que = []
            while queue:
                # 针对每一个pop出的结点,将值计入result中,并且将其结点推入queue中
                temp = queue.pop(0)
                temp_res.append(temp.val)
                if temp.left:
                    temp_que.append(temp.left)
                if temp.right:
                    temp_que.append(temp.right)
            queue = temp_que
            result.append(temp_res)
        return result

3.利用递归解决的问题

  1. 二叉树的最大深度
class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        elif not root.left and not root.right:
            return 1
        else:
            leftdepth = self.maxDepth(root.left)
            rightdepth = self.maxDepth(root.right)
            if leftdepth > rightdepth:
                return leftdepth+1
            else:
                return rightdepth+1
  1. 平衡二叉树
class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        result = maxDepth(root)
        return result != -1
# 改进版maxdeopth, 如果平衡,则返回深度,不平衡返回-1
def maxDepth(root):
    if not root:
        return 0
    else:
        ldepth = maxDepth(root.left)
        if ldepth == -1:
            return -1
        rdepth = maxDepth(root.right)
        if rdepth == -1:
            return -1

        c = ldepth - rdepth
        if abs(c) <= 1:
            return max(ldepth, rdepth) + 1
        else:
            # -1 代表不平衡
            return -1
  1. 对称二叉树
    判断左子树和右子树是否成镜像:
    左子树的右子树和右子树的左子树成镜像
    左子树的左子树和右子树的左子树成镜像
class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        if root:
            return isMirror(root.left, root.right)
        else:
            return True

def isMirror(root1, root2):
    if not root1 and not root2:
        return True
    if not root1 or not root2:
        return False
    if root1.val != root2.val:
        return False
    return isMirror(root1.left, root2.right) and isMirror(root1.right, root2.left)

        
  1. 路径总和
# 递归
class Solution:
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
        if not root:
            return False
        if not root.left and not root.right:
            return root.val == sum
        return self.hasPathSum(root.left, sum-root.val) or self.hasPathSum(root.right, sum-root.val)
        
# 非递归 (利用队列实现)
class Solution:
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
        if not root:
            return False
        que_node = [root]
        que_val = [root.val]
        while que_node and que_val:
            # 弹出 node 和 val
            cur_node = que_node.pop(0)
            cur_val = que_val.pop(0)
            # 如果node是叶节点,返回结果
            if isLeaf(cur_node) and cur_val == sum:
                return True
            # 添加子节点
            if cur_node.left:
                que_node.append(cur_node.left)
                que_val.append(cur_val + cur_node.left.val)
            if cur_node.right:
                que_node.append(cur_node.right)
                que_val.append(cur_val + cur_node.right.val)
        return False           
            
def isLeaf(root):
    return root and not root.left and not root.right
        
  1. 从中序遍历和后序遍历序列构造二叉树
# 初始版本
# 利用了python的一些特性,但效果不是很好
class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        if not inorder:
            return
        if len(inorder) == 1:
            return TreeNode(inorder[0])
            
        # 根据后序得到根节点的值和根节点在中序中的位置
        root_val = postorder[-1]
        in_root_index = inorder.index(root_val)
        # 以根节点为分隔,得到左子树和右子树的中序遍历和后序遍历
        in_left = inorder[:in_root_index]
        in_right = inorder[in_root_index+1:]
        l1 = len(in_left)
        l2 = len(in_right)
        post_left = postorder[:l1]
        post_right = postorder[l1: l1+l2]

        # 递归得到结果
        root = TreeNode(root_val)
        root.left = self.buildTree(in_left, post_left)
        root.right = self.buildTree(in_right, post_right)

        return root
# 同样的思路,效果更好,注意看注释
class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        def travesal(in_left, in_right):
            # in_left, in_right表示当前inorder在原始inorder上面的区间索引
            # 初始入口为 travesal(0, n-1)

            # 停止条件1 None
            if in_left > in_right:
                return None
            
            
            root = TreeNode(postorder.pop()) # 此时后序弹出最后一个元素为根节点
            index_root = indexmap[root.val] # 找到根节点在中序中索引作为切割点
            
            # 以根节点索引为切割点,左边是左子树的中序遍历, 右边是右子树的中序遍历,进行递归
            # 注意这里一定要先构建右子树,因为每递归一次,后序就会弹出最后一个元素
            # 根据后序的左右中顺序,这个弹出的元素总会是当前根节点的右子树的根节点
            root.right = travesal(index_root+1, in_right)
            root.left = travesal(in_left, index_root-1)
            

            return root

        # 构建中序遍历的索引值哈希表,这一步是为了减少每次查询根节点位置时候的时间
        indexmap = {value:index for index, value in enumerate(inorder)}
        return travesal(0, len(inorder)-1)
  1. 利用中序遍历和前序遍历序列构造二叉树
class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        def travesal(in_left, in_right):
            # in_left, in_right表示当前inorder在原始inorder上面的区间索引
            # 初始入口为 travesal(0, n-1)

            # 停止条件1 None
            if in_left > in_right:
                return None
            
            root = TreeNode(postorder.pop()) # 此时后序弹出最后一个元素为根节点
            index_root = indexmap[root.val] # 找到根节点在中序中索引作为切割点
            
            # 以根节点索引为切割点,左边是左子树的中序遍历, 右边是右子树的中序遍历,进行递归
            # 注意这里一定要先构建右子树,因为每递归一次,后序就会弹出最后一个元素
            # 根据后序的左右中顺序,这个弹出的元素总会是当前根节点的右子树的根节点
            root.right = travesal(index_root+1, in_right)
            root.left = travesal(in_left, index_root-1)
            
            return root

        # 构建中序遍历的索引值哈希表,这一步是为了减少每次查询根节点位置时候的时间
        indexmap = {value:index for index, value in enumerate(inorder)}
        return travesal(0, len(inorder)-1)

你可能感兴趣的:(算法,Leetcode,算法,二叉树,数据结构)