1.题目
这道题是2024-2-22的签到题,题目难度为中等。
考察知识点为递归。
题目链接:889. 根据前序和后序遍历构造二叉树 - 力扣(LeetCode)
给定两个整数数组,preorder
和 postorder
,其中 preorder
是一个具有 无重复 值的二叉树的前序遍历,postorder
是同一棵树的后序遍历,重构并返回二叉树。
如果存在多个答案,您可以返回其中 任何 一个。
2.思路
昨天和家里人玩的太嗨了就只做了题目没有及时更新题解,小小的偷个懒嘿嘿。
题目难点
前两天的题目分别是根据前序与中序遍历序列构造二叉树、从中序与后序遍历序列构造二叉树,因此大体思路我们还是一样的,定义递归函数来构建树。但这道题有一个难点:我们如何确定每棵树的左子树总结点个数leftNum和右子树总结点个数rightNum,前两天我们是根据中序遍历序列来确定leftNum和rightNum的。那么我们重新看一下每一个遍历的顺序序列:
前序遍历数组:[根节点值,(左子树的所有结点值),(右子树的所有结点值)]
中序遍历数组:[(左子树的所有结点值),根节点值,(右子树的所有结点值)]
后序遍历数组:[(左子树的所有结点值),(右子树的所有结点值),根节点值]
很明显我们根据之前的思路只能确定根节点的值,那该怎么办呢?其实这个就要考察我们对树的遍历进一步理解了,下面是我一步一步的思路(可能有点啰嗦,但清晰易懂)。
确定左子树和右子树结点个数
对于同一棵树,它的前序遍历数组下标第一位肯定是根节点,前序遍历数组下标第二位肯定是它的左子结点(这个有点意思了,细品😜)当然我也画了一个简略图方便大家理解。
这样我们是不是可以找到左子树的根节点值了呀,下面这一步是见证奇迹的时候了。对于后序遍历数组,它的下标最后一位肯定是根节点对吧,那么我们是不是可以借助前面得到的左子树根节点值来确定它在后序遍历数组的下标索引呢?下面是我画的一个图
有了左子树根节点的值,我们可以利用python的index函数来找到它在后序遍历序列中的下标索引,而后序遍历数组中从遍历开始下标pos_start到左子树根节点下标leftRootIndex这个区间是不是就是左子树区间呢?没错,这样我们就可以求出左子树结点个数leftNum了,然后就可以根据当前数组的pos_start和pos_end来进行相关逻辑相减得到右子树结点个数rightNum。
构建递归函数
同样的,我们需要定义一个递归函数构建我们的树,函数有4个参数,分别是:
pre_start:当前树的前序遍历开始索引
pre_end:当前树的前序遍历结束索引
pos_start:当前树的后序遍历开始索引
pos_end:当前树的后续i办理结束索引
递归核心逻辑
对于一个正常的遍历序列,它的开始索引肯定是小于等于结束索引的(无论是前序遍历还是后序遍历),因此如果当pre_start>pre_end或pos_start>pos_end的时候,它需要返回None。
如果到了这里你觉得结束了那么不好意思,你这道题要卡住了,为啥,因为这道题我们有一个确定左子树根节点这一操作,根据前面的思路,当前树的左子结点下标索引是排第2位的,即:
因此我们需要对这个preLeftRootIndex进行判断,如果不符合规则(leftRootIndex > pre_end),那么说明这个左子结点是个叶子节点(左子结点没有子结点了),则可以直接返回结点了。否则我们需要确定它在后序遍历数组中的下标索引:
然后我们利用pos_start和刚才得到的posLeftRootIndex就可以确定左子树的长度了,不理解的还是可以看上面的图。
其中,preEnd-preStart表示当前树的所有子树节点个数(leftNum + rightNum)。
到此我们就可以得到两颗子树的下标索引了,接下来就是构建树了,我们还是利用TreeNode来构建结点,然后我们还需要推导两颗子树的4个参数公式:
左子树参数公式
右子树参数公式
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
class Solution:
def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
# 结点列表的长度
length = len(preorder)
# 递归函数,用于构建树
def build(pre_start,pre_end,pos_start,pos_end):
# 如果下标索引start>end,则返回None
if pre_start > pre_end:
return None
# 先构建当前树的根节点
root = TreeNode(preorder[pre_start])
# 当前树的左子树根节点下标索引(左子结点下标索引)
pre_leftRoot = pre_start + 1
# 如果当前索引超出范围,则直接返回结点
# 说明这个左子树是一个左子结点
if pre_leftRoot > pre_end:
return root
# 求后序遍历数组中左子树根结点的下标索引
pos_leftRoot = postorder.index(preorder[pre_leftRoot])
# 左子树所有结点个数
leftNum = pos_leftRoot - pos_start + 1
# 右子树所有节点个数
rightNum = pre_end - pre_start - leftNum
# 递归构建左子树
root.left = build(pre_start+1,pre_start+leftNum,pos_start,pos_start+leftNum-1)
# 递归构建右子树
root.right = build(pre_start+leftNum+1,pre_end,pos_start+leftNum,pos_end-1)
return root
return build(0,length-1,0,length-1)