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