4.剑指offer-重建二叉树(python版)
本题知识点: 树
题目描述:
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/
9 20
/
15 7
解题思路:
用前序遍历找到根结点。
用根结点在中序遍历中切开左右子树,递归重建二叉树。
方法:分治思想
经验总结:二叉树的问题一般都是分治思想,递归去做。因为二叉树本身就是递归定义的。
题目分析:
前序遍历特点: 节点按照 [ 根节点 | 左子树 | 右子树 ] 排序,以题目示例为例:[ 3 | 9 | 20 15 7 ]
中序遍历特点: 节点按照 [ 左子树 | 根节点 | 右子树 ] 排序,以题目示例为例:[ 9 | 3 | 15 20 7 ]
根据题目描述输入的前序遍历和中序遍历的结果中都不含重复的数字,其表明树中每个节点值都是唯一的。
根据以上特点,可以按顺序完成以下工作:
前序遍历的首个元素即为根节点 root 的值;
在中序遍历中搜索根节点 root 的索引 ,可将中序遍历划分为 [ 左子树 | 根节点 | 右子树 ] 。
根据中序遍历中的左(右)子树的节点数量,可将前序遍历划分为 [ 根节点 | 左子树 | 右子树 ] 。
自此可确定 三个节点的关系 :1.树的根节点、2.左子树根节点、3.右子树根节点(即前序遍历中左(右)子树的首个元素)。
子树特点: 子树的前序和中序遍历仍符合以上特点,以题目示例的右子树为例:前序遍历:[20 | 15 | 7],中序遍历 [ 15 | 20 | 7 ] 。
根据子树特点,我们可以通过同样的方法对左(右)子树进行划分,每轮可确认三个节点的关系 。此递推性质让我们联想到用 递归方法 处理。
递归解析:
递推参数: 前序遍历中根节点的索引pre_root、中序遍历左边界in_left、中序遍历右边界in_right。
终止条件: 当 in_left > in_right ,子树中序遍历为空,说明已经越过叶子节点,此时返回 nullnull 。
递推工作:
建立根节点root: 值为前序遍历中索引为pre_root的节点值。
搜索根节点root在中序遍历的索引i: 为了提升搜索效率,本题解使用哈希表 dic 预存储中序遍历的值与索引的映射关系,每次搜索的时间复杂度为 O(1)O(1) 。
构建根节点root的左子树和右子树: 通过调用 recur() 方法开启下一层递归。
左子树: 根节点索引为 pre_root + 1 ,中序遍历的左右边界分别为 in_left 和 i - 1。
右子树: 根节点索引为 i - in_left + pre_root + 1(即:根节点索引 + 左子树长度 + 1),中序遍历的左右边界分别为 i + 1 和 in_right 。
返回值: 返回 root ,含义是当前递归层级建立的根节点 root 为上一递归层级的根节点的左或右子节点。
代码如下:
self.dic, self.po = {}, preorder
for i in range(len(inorder)):
self.dic[inorder[i]] = i
return self.recur(0, 0, len(inorder) - 1)
def recur(self, pre_root, in_left, in_right):
if in_left > in_right: return # 终止条件:中序遍历为空
root = TreeNode(self.po[pre_root]) # 建立当前子树的根节点
i = self.dic[self.po[pre_root]] # 搜索根节点在中序遍历中的索引,从而可对根节点、左子树、右子树完成划分。
root.left = self.recur(pre_root + 1, in_left, i - 1) # 开启左子树的下层递归
root.right = self.recur(i - in_left + pre_root + 1, i + 1, in_right) # 开启右子树的下层递归
return root # 返回根节点,作为上层递归的左(右)子节点