106. 从中序与后序遍历序列构造二叉树
给定两个整数数组 inorder
和 postorder
,其中 inorder
是二叉树的中序遍历, postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
示例 1:
输入: inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]
示例 2:
输入: inorder = [-1], postorder = [-1]
输出:[-1]
提示:
- 1 ≤ i n o r d e r . l e n g t h ≤ 3000 1 \leq inorder.length \leq 3000 1≤inorder.length≤3000
postorder.length == inorder.length
- − 3000 ≤ i n o r d e r [ i ] , p o s t o r d e r [ i ] ≤ 3000 -3000 \leq inorder[i], postorder[i] \leq 3000 −3000≤inorder[i],postorder[i]≤3000
inorder
和postorder
都由 不同 的值组成postorder
中每一个值都在inorder
中inorder
保证是树的中序遍历postorder
保证是树的后序遍历
解法一(递归+分治+Map哈希)
思路分析:
- 对于该题,首先思考;中序遍历为:左中右,后序遍历为:左右中,因此通过后序遍历可以确认二叉树的根节点,然后通过根节点可以对中序遍历进行切割成:左中序、右中序;然后根据得到的左中序长度,可以对后序遍历进行切割成:左后序、右后序
- 以此类推,通过递归分治的方式,可以从根节点建立一个二叉树。
- 同时思考递归的参数和返回值,因为题目要求构造一个二叉树,所以 返回值类型为
TreeNode
,然后对于递归的参数则包括,中序遍历数组、后序遍历数组、中序数组起始位置、中序数组末尾位置、后序数组起始位置、后序数组末尾位置。 - 对于递归的边界条件,则当后序遍历数组为
null
时,返回null
,当由后序遍历索引起始及末尾位置得;数组长度为1时,直接返回 - 对于递归的过程,则是构造中间节点,以及递归构造左右节点
- 同时对于如何根据后序数组,对中序数组进行分割,可以使用
Map
哈希表的方式,避免对中序数组进行反复查询。
实现代码如下:
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if (postorder == null)
return null; // 边界条件
// 构造哈希表
Map<Integer, Integer> inMap = new HashMap<>();
for (int i = 0; i < inorder.length; i++) {
inMap.put(inorder[i], i);
}
return doBuildTree(inorder, postorder, inMap, 0, inorder.length-1, 0, postorder.length-1);
}
private TreeNode doBuildTree(int[] inorder, int[] postorder, Map<Integer, Integer> inMap, int inS, int inE, int postS, int postE) {
if (inE < 0 || postE < 0 || inS > inE || postS > postE || inS >= inorder.length || postS >= postorder.length) // 考虑边界问题
return null;
// 根据后序遍历数组 末尾索引 获取该子树根节点值
int rootValue = postorder[postE];
TreeNode node = new TreeNode(rootValue); // 构造二叉树
if (postS == postE) // 若此时后序数组 起始索引和末尾索引相等 说明为叶子节点
return node; // 直接返回
// 根据根节点值 对中序数组进行分割 获取分割位置索引
int index = inMap.get(rootValue);
// 递归获取左右子树
node.left = doBuildTree(inorder, postorder, inMap, inS, index-1, postS, postS+index-1-inS);
node.right = doBuildTree(inorder, postorder, inMap, index+1, inE, postS+index-inS, postE-1);
return node;
}
}
提交结果如下:
解答成功:
执行耗时:2 ms,击败了62.35% 的Java用户
内存消耗:43.5 MB,击败了13.55% 的Java用户
复杂度分析:
- 时间复杂度: O ( n + m ) O(n+m) O(n+m),需要遍历数组
- 空间复杂度: O ( n + m ) O(n+m) O(n+m),考虑递归对空间的消耗
优化解法一
思路分析:
- 通过对解法一代码的执行流程,发现递归函数
doBuildTree
中的inorder
参数可以省略 - 且对于
doBuildTree
函数中的边界问题判断,由于初始inE
与PostE
均为len-1
,inS
与postS
初始为0,因此对于inE < 0
的判断与inS >= inorder.length
的判断包含在inS > inE
中,可省略
实现代码如下:
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if (postorder == null)
return null; // 边界条件
// 构造哈希表
Map<Integer, Integer> inMap = new HashMap<>();
for (int i = 0; i < inorder.length; i++) {
inMap.put(inorder[i], i);
}
return doBuildTree(postorder, inMap, 0, inorder.length-1, 0, postorder.length-1);
}
private TreeNode doBuildTree(int[] postorder, Map<Integer, Integer> inMap, int inS, int inE, int postS, int postE) {
if (inS > inE || postS > postE) // 考虑边界问题
return null;
// 根据后序遍历数组 末尾索引 获取该子树根节点值
int rootValue = postorder[postE];
TreeNode node = new TreeNode(rootValue); // 构造二叉树
if (postS == postE) // 若此时后序数组 起始索引和末尾索引相等 说明为叶子节点
return node; // 直接返回
// 根据根节点值 对中序数组进行分割 获取分割位置索引
int index = inMap.get(rootValue);
// 递归获取左右子树
node.left = doBuildTree(postorder, inMap, inS, index-1, postS, postS+index-1-inS);
node.right = doBuildTree(postorder, inMap, index+1, inE, postS+index-inS, postE-1);
return node;
}
}
提交结果如下:
解答成功:
执行耗时:2 ms,击败了62.35% 的Java用户
内存消耗:43.6 MB,击败了10.93% 的Java用户
复杂度分析:
- 时间复杂度: O ( n + m ) O(n+m) O(n+m),遍历中序数组和后序数组
- 空间复杂度: O ( n + m ) O(n+m) O(n+m),考虑每层递归传递参数对空间消耗。
解法二(递归+分治+Map)
思路分析:
- 跟据官方题解,将中序数组、后序数组,以及提交查询的Map变量,均改为全局遍历,即不需要作为递归函数参数,可在递归函数内访问。
- 因为后序遍历中,最后一个元素为子树的根节点,所以先递归获取右子树,再递归获取左子树
实现代码如下:
class Solution {
int[] inorder; // 中序遍历数组
int[] postorder; // 后序遍历数组
Map<Integer, Integer> inMap; // 中序遍历数组 索引表
int postIndex;
public TreeNode buildTree(int[] inorder, int[] postorder) {
if (postorder == null)
return null; // 边界条件
this.inorder = inorder;
this.postorder = postorder;
postIndex = postorder.length-1;
// 构造哈希表
inMap = new HashMap<>();
for (int i = 0; i < inorder.length; i++) {
inMap.put(inorder[i], i);
}
return doBuildTree(0, inorder.length-1);
}
private TreeNode doBuildTree(int inLeft, int inRight) {
if (inLeft > inRight) // 说明此时为空树
return null;
int value = postorder[postIndex]; // 根据postIndex 来确定当前子树 中节点值
TreeNode node = new TreeNode(value);
// 根据 中间节点值 获取分割中序数组索引
int index = inMap.get(value);
postIndex--; // 移动所指向的根节点
// 先获取右子树
node.right = doBuildTree(index+1, inRight);
// 再获取左子树
node.left = doBuildTree(inLeft, index-1);
return node;
}
}
提交结果如下:
解答成功:
执行耗时:1 ms,击败了99.58% 的Java用户
内存消耗:43.2 MB,击败了32.11% 的Java用户
复杂度分析:
- 时间复杂度:
O
(
n
)
O(n)
O(n),
n
表示树的节点个数 - 空间复杂度:
O
(
n
)
O(n)
O(n),需要使用
O
(
n
)
O(n)
O(n)的空间存储哈希表,同时
O
(
h
)
O(h)
O(h)的空间进行递归(即二叉树的高度),且
h < n