从二叉树开始刷起(一)—— 熟悉递归套路

最近想着系统地刷一些题,找了许多资料与课程,最终发现labuladong的刷题方案比较适合我,于是开始按照labuladong 的算法小抄开始刷题,希望自己能够坚持下来,要一直成长,加油。

226. 翻转二叉树

翻转二叉树:

image-20211009142708995

# 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 invertTree(self, root: TreeNode) -> TreeNode:
        # 核心思路就是递归反转左右子树就可以了
        if not root:return
        # 先让左右子树进行翻转
        root.right, root.left = root.left, root.right
        # 递归调用函数让他们翻转就行了
        self.invertTree(root.left)
        self.invertTree(root.right)
        return root

题解如上,其实核心的点在于:只要把二叉树上的每一个节点的左右子节点进行交换,最后的结果就是完全翻转之后的二叉树。关键思路在于我们发现翻转整棵树就是交换每个节点的左右子节点,于是我们把交换左右子节点的代码放在了前序遍历的位置。

总结一下核心突破点:识别可以用哪一种遍历方式+具体要对节点进行什么操作。

我们按照这个思路继续来看几道题:

116. 填充每个节点的下一个右侧节点指针

image-20211009143958367

image-20211009144011852

按照上面的思路,我们最重要的其实就是要找到对单个节点进行的操作。这里的核心处理逻辑是一个节点的next指针指向另一个节点,即:node1.next = node2,然后我们需要从根节点开始,把每一个的左右子树都进行这样的操作,但需要注意的是还有不同子树间节点的连接,也需要连接起来。【注意递归函数体上来就应该把递归返回的条件写出来】

"""
# Definition for a Node.
class Node:
    def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None):
        self.val = val
        self.left = left
        self.right = right
        self.next = next
"""

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        
        #定义递归函数体
        def connect_two_nodes(node1,node2):
            # 递归返回条件不能忘
            if not node1 or not node2: return
            # 核心处理逻辑
            node1.next = node2
            
            # 对两个节点内部各自进行连接
            connect_two_nodes(node1.left,node1.right)
            connect_two_nodes(node2.left,node2.right)

            # 对两个节点之间进行连接操作
            connect_two_nodes(node1.right,node2.left)

        if not root: return None
        connect_two_nodes(root.left,root.right)
        return root

是不是有点意思,再来一题练练手:

114. 二叉树展开为链表

image-20211009145749700

还是同样的套路,先思考我们要对一个节点做什么,再将这样的作法推广到其他节点上去。可以看到,其实是需要对每一个节点:把它的右子树断开接到左子树末尾上面去,需要注意的是,不需要对根节点做额外的操作,只需要作用于左右子树递归完成即可。

# 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 flatten(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        if not root:return
        self.flatten(root.left)
        self.flatten(root.right)

        #保存拆分之前的右子树
        right = root.right
        # 右子树是原先的左子树
        root.right = root.left
        # 现在再把左子树置为空
        root.left = None
        # 找到左边子树(现在已经变为右子树了)的最后一个节点,将原先的右子树(临时变量right)连接到最后一个节点上去
        while root.right:
            root = root.right
        # 原右子树连接到现在右子树的最后一个节点即可
        root.right = right

我们继续:

654. 最大二叉树

image-20211009154343358

image-20211009154359688

遇到这种提很容易思路有,但是转化为代码难,还是按照我们之前的套路,需要认定出来最小的子问题是要做什么。最小的子问题应该是,在一个区间范围内,找到区间内的最大值,然后以该值为根节点,构建树,对于该值的左右区间继续递归分别构建左右子树。

class Solution:
    def constructMaximumBinaryTree(self, nums: List[int]) -> Optional[TreeNode]:
        if not nums:return None
        max_num = max(nums)
        max_num_index = nums.index(max_num)
        root = TreeNode()
        root.val = max_num
        root.left = self.constructMaximumBinaryTree(nums[:max_num_index])
        root.right= self.constructMaximumBinaryTree(nums[max_num_index+1:])
        return root

继续

105. 从前序与中序遍历序列构造二叉树

image-20211009160547404

来来来,还是按照基本思路来弄。搞清楚我们最小子问题要做啥,其实就是要根据前序遍历的结果确定根节点元素,然后根据中序遍历结果找到对应的左右子树对应的区间即可,所以我们需要至少四个参数传入:

# 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 buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:

        def build(preorder, pre_l, pre_r, inorder, in_l, in_r):
            # 确定递归截止条件
            if pre_l > pre_r or in_l > in_r:return
            # 通过前序遍历第一个节点,找到根节点
            root_val = preorder[pre_l]
            # 找到根节点在后序遍历list中的索引,以便切分
            root_index_in = inorder.index(root_val)
            
            # 构造树的根节点
            root = TreeNode(root_val)
            # 永远盯着递归函数的定义,不要盯着细节
            # 画图确定索引,先可以确定后续遍历中的索引,再根据后续遍历中左子树的长度确定其他长度
            root.left = build(preorder, pre_l+1 , pre_l+root_index_in-in_l,
                              inorder, in_l , root_index_in-1)
            root.right = build(preorder, pre_l+root_index_in-in_l+1 ,pre_r, 
                               inorder, root_index_in+1, in_r)

            return root
        
        # 调用套路
        return build(preorder, 0, len(preorder)-1,
                     inorder, 0, len(inorder)-1)

同样的套路可以解决另外一道题:

106. 从中序与后序遍历序列构造二叉树

image-20211009165609956

根据上面的套路来即可,关键是找准对应的左右边界:

# 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 buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
        def build(inorder, in_left, in_right,
                  postorder, post_left, post_right):
            if  in_left > in_right or post_left > post_right: return None

            # 先找到根节点,一定要注意这个索引不是固定的0/-1
            root_val = postorder[post_right]
            # 再找到根节点元素在中序遍历中的索引
            in_root_index = inorder.index(root_val)

            # 构建树,先构建根节点
            root = TreeNode(root_val)

            # 再构建左右子树
            size_len = in_root_index - in_left
            root.left = build(inorder,in_left , in_root_index-1,
                              postorder,post_left , post_left+size_len-1)
            root.right = build(inorder, in_root_index+1, in_right,
                               postorder,post_left+size_len , post_right-1)

            return root

        return build(inorder, 0, len(inorder)-1,
                    postorder, 0, len(postorder)-1)

一下子干了两题,爽吧,主要是需要根据左右子树长度区分边界。再看一题

652. 寻找重复的子树

image-20211009172121742

这道题,看似很让人头疼啊,不要慌,先想一想,我们要解决啥子问题。

子问题的核心是要将各个子树表示出来,这里我们用到了树序列化的方法,将子树表示成子字符串,作为hashmap的K,V即是对应出现的次数,如果出现次数达到2,则将对应的子树加入结果列表中:

# 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 findDuplicateSubtrees(self, root: Optional[TreeNode]) -> List[Optional[TreeNode]]:
        res = []
        temp = {}
        def traverse(root):
            # 先进行子树的表示,进行子树的序列化,后序遍历的方式进行保存
            if not root:return "#"
            left = str(traverse(root.left))
            right = str(traverse(root.right))
            str_sub_tree = left + "," + right+ "," + str(root.val)
            # 将所有的子树加入临时map中,若有重复的,则将对应子树加入结果list中,多次重复只加入一次
            if str_sub_tree in temp:
                temp[str_sub_tree] += 1
                if temp[str_sub_tree] == 2:
                    res.append(root)
            else:
                temp[str_sub_tree] = 1
            return str_sub_tree
        
        traverse(root)
        return res

由此我们引申出更大的一个篇章就是二叉树的序列化与反序列化。下一篇我们继续来探究。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值