题目: 输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
限制:
0 <= 节点个数 <= 5000
回顾
首先回顾下二叉树的遍历
- 前序遍历:根结点 —> 左子树 —> 右子树(先遍历根节点,然后左右)
- 中序遍历:左子树—> 根结点 —> 右子树(在中间遍历根节点)
- 后序遍历:左子树 —> 右子树 —> 根结点(最后遍历根节点)
- 层次遍历:按层次遍历
所谓的前序、中序、后续,就是对根节点而言的,左右的遍历顺序不变,前序就是根节点最先遍历,然后左右;中序就是把根节点放在中间遍历;后序则是把根节点放在最后遍历
分析:根据前序和中序遍历的特点,可以得出以下结论
- 前序遍历的首个元素即为root根节点
- 在中序遍历中搜索根节点的索引,可以把中序遍历分解为[左子树|根节点|右子树]
- 根据中序遍历中左/右子树的节点数量,可以将前序遍历划分为[根节点|左子树|右子树]
通过以上结论我们可以确定3个节点的关系
- 树的根节点
- 左子树的根节点(前序遍历中左子树的首个元素)
- 右子树的根节点(前序遍历中右子树的首个元素)
解题思路
树型结构大多使用递归搞定:
- 递归参数:
- preRoot:前序遍历的根节点的位置
- inLeft:中序遍历的左边界的位置
- inRight:中序遍历的右边界的位置
- 终止条件: 左边界大于右边界
- 递归流程:
- 找到根节点root,root值为前序遍历第一个值
- 找到root在中序遍历中的位置,此处使用哈希表保存了中序遍历中值与索引的映射关系
- 递归调用root节点的左子树与右子树
- 根节点在中序索引中的位置: i
- 左子树:根节点为前序遍历preRoot + 1,根节点的左右边界为:inLeft 和 i - 1
- 左子树的长度:i - inleft
- 右子树:根节点为前序遍历preRoot + 左子树的长度 + 1 = preRoot + i - inleft + 1, 左右边界为:i + 1 和 inRight;
代码
Map<Integer, Integer> dic = new HashMap<>();
int[] po;
public TreeNode buildTree(int[] preorder, int[] inorder) {
po = preorder;
for (int i = 0; i < inorder.length; i++) {
dic.put(inorder[i], i);
}
return recur(0, 0, inorder.length - 1);
}
/**
*
* @param preRoot 前序遍历的根节点的位置
* @param inLeft 中序遍历的左边界的位置
* @param inRight 中序遍历的右边界的位置
* @return
*/
TreeNode recur(int preRoot, int inLeft, int inRight) {
// 左边界大于右边界返回
if (inLeft > inRight) return null;
// 创建节点
TreeNode root = new TreeNode(po[preRoot]);
// 查找根节点在中序遍历中的位置
int i = dic.get(po[preRoot]);
// 左子树,前序遍历的根节点 + 1 是左子树的根节点
root.left = recur(preRoot + 1, inLeft, i - 1);
// 右子树的根,就是前序遍历右子树的第一个,相当于当前根节点 + 左子树的长度 + 1
// preRoot是当前的根,i是当前根的位置, 左子树的长度 = i - inLeft
// 右子树的根 = preRoot + ( i - inLeft ) + 1 = preRoot + i - inLeft + 1
root.right = recur(preRoot + i - inLeft + 1, i + 1, inRight);
return root;
}