本文是力扣LeeCode-106、从中序与后序遍历序列构造二叉树 LeeCode-105、从前序与中序遍历序列构造二叉树 学习与理解过程,本文仅做学习之用,对本题感兴趣的小伙伴可以出门左拐LeeCode。
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 <= inorder.length <= 3000
- postorder.length == inorder.length
- -3000 <= inorder[i], postorder[i] <= 3000
- inorder 和 postorder 都由 不同 的值组成
- postorder 中每一个值都在 inorder 中
- inorder 保证是树的中序遍历
- postorder 保证是树的后序遍历
思路
1. 判断是否为分治问题
原问题定义为从 postorder 和 inorder 构建二叉树,是一个典型的分治问题。
问题可以分解
:从分治的角度切入,我们可以将原问题划分为两个子问题:构建左子树、构建右子树,加上一步操作
:初始化根节点。而对于每棵子树(子问题),我们仍然可以复用以上划分方法,将其划分为更小的子树(子问题),直至达到最小子问题(空子树)时终止。子问题是独立的
:左子树和右子树是相互独立的,它们之间没有交集。在构建左子树时,我们只需关注中序遍历和前序遍历中与左子树对应的部分。右子树同理。子问题的解可以合并
:一旦得到了左子树和右子树(子问题的解),我们就可以将它们链接到根节点上,得到原问题的解。
2. 如何划分子树
根据以上分析,这道题可以使用分治来求解,但如何通过后序遍历 postorder 和中序遍历 inorder 来划分左子树和右子树呢?
根据定义,postorder 和 inorder 都可以划分为三个部分。
‧ 后序遍历:[ 左子树 | 右子树 | 根节点] ,例如上图的树对应 [ 9 | 15 7 20 | 3 ] 。
‧ 中序遍历:[ 左子树 | 根节点 | 右子树 ] ,例如上图的树对应 [ 9 | 3 | 15 20 7 ] 。
所以根据上述定义,我们可以通过从后往前遍历后序遍历的数组,拿到遍历的值作为根节点去切割中序遍历数组
比如:拿到3,可以将中序数组切割为 [ 9 | 3 | 15 20 7 ]
3. 基于变量描述子树区间
根据以上划分方法,我们已经得到根节点、左子树、右子树在 postorder和 inorder 中的索引区间。而为了描述这些索引区间,我们需要借助几个指针变量。
- 将当前树的根节点在 postorder中的索引记为 postRootIndex 。
- 将当前树的根节点在 inorder 中的索引记为m。
- 将当前树在 inorder 中的索引区间记为 [indexLeft, indexRight] ,这个区间会随着切割inorder后变化。
代码实现
class Solution {
private int postRootIndex = 0; //当前树的根节点在 postorder中的索引
private HashMap<Integer, Integer> inorderMap = new HashMap<>();
private int[] gInorder;
private int[] gPostorder;
public TreeNode buildTree(int[] inorder, int[] postorder) {
gInorder = inorder;
gPostorder = postorder;
int length = gPostorder.length;
postRootIndex = length-1;
// 初始化哈希表,存储 inorder 元素到索引的映射,避免重复遍历inorder
for(int i=0;i<inorder.length;i++){
inorderMap.put(inorder[i],i);
}
return dfs(0,length-1);
}
TreeNode dfs(int indexLeft,int indexRight){
// 子树区间为空时终止
if(indexLeft>indexRight)return null;
int rootValue = gPostorder[postRootIndex];
postRootIndex--;
// 初始化根节点
TreeNode root = new TreeNode(rootValue);
// 查询 m ,从而划分左右子树,当前树的根节点在 inorder 中的索引
int m = inorderMap.get(rootValue);
//先构建左子树,还是右子树,需要根据提供的后序或者前序数组中节点的值的排列顺序决定
//比如:此题是后序遍历,从后往前遍历postOrder,并生成节点,所以是: 中、右、左
// 子问题:构建右子树
root.right = dfs(m+1,indexRight);
// 子问题:构建左子树
root.left = dfs(indexLeft,m-1);
return root;
}
}
注: 先构建左子树,还是右子树,需要根据提供的后序或者前序数组中节点的值的排列顺序决定 比如:此题是后序遍历,从后往前遍历postOrder,并生成节点,所以是: 中、右、左
- 时间复杂度为 𝑂(𝑛)
- 空间复杂度为 𝑂(𝑛)
105、从前序与中序遍历序列构造二叉树
给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例 1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
示例 2:
输入: preorder = [-1], inorder = [-1]
输出: [-1]
提示:
- 1 <= preorder.length <= 3000
- inorder.length == preorder.length
- -3000 <= preorder[i], inorder[i] <= 3000
- preorder 和 inorder 均 无重复 元素
- inorder 均出现在 preorder
- preorder 保证 为二叉树的前序遍历序列
- inorder 保证 为二叉树的中序遍历序列
思路
此题和106
的解题思路是一致的,但是需要注意:
先构建左子树,还是右子树,需要根据提供的后序或者前序数组中节点的值的排列顺序决定 比如:此题是前序遍历,从前往后遍历preOrder,并生成节点,所以是: 中、左、右
代码实现
class Solution {
private int preRootIndex = 0; //当前树的根节点在 preorder中的索引
private HashMap<Integer, Integer> inorderMap = new HashMap<>();
private int[] gInorder;
private int[] gPreorder;
public TreeNode buildTree(int[] preorder, int[] inorder) {
gInorder = inorder;
gPreorder = preorder;
int length = gPreorder.length;
// 初始化哈希表,存储 inorder 元素到索引的映射,避免重复遍历inorder
for(int i=0;i<inorder.length;i++){
inorderMap.put(inorder[i],i);
}
return dfs(0,length-1);
}
TreeNode dfs(int indexLeft,int indexRight){
// 子树区间为空时终止
if(indexLeft>indexRight)return null;
int rootValue = gPreorder[preRootIndex];
preRootIndex++;
// 初始化根节点
TreeNode root = new TreeNode(rootValue);
// 查询 m ,从而划分左右子树,当前树的根节点在 inorder 中的索引
int m = inorderMap.get(rootValue);
// 子问题:构建左子树
root.left = dfs(indexLeft,m-1);
// 子问题:构建右子树
root.right = dfs(m+1,indexRight);
return root;
}
}
- 时间复杂度为 𝑂(𝑛)
- 空间复杂度为 𝑂(𝑛)
最重要的一句话:做二叉树的题目,首先需要确认的是遍历顺序
大佬们有更好的方法,请不吝赐教,谢谢