题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15,20,7] 返回如下的二叉树:3 / \ 9 20 / \ 15 7
限制: 0 <= 节点个数 <= 5000
方法一(递归)
1.解题思路
关于二叉树的递归,有一个基本的套路,只要按照这个套路,分三步走,大部分的二叉树相关题目都能迎刃而解。
- 寻找递归终止条件,一般为递归函数不能再走下去,或者二叉树为空。
- 把递归分层,我们不需要关注递归函数具体是怎么一层一层传递实现的,只要关注当前节点X,X的左孩子,X的右孩子,也就是当前层的变化。然后我们确定需要从X的左孩子获取什么,从X的右孩子获取什么。
- 返回当前层的X。
回到这个题目,很显然递归的终止条件是不能再从前序遍历取值或者不能再从中序遍历取值,然后第二步,确定当前层的X,X的左孩子和X的右孩子,X自然是前序遍历的第一个节点,我们从中序遍历找到X对应的位置,那么X左边的便是左子树,X右边的便是右子树。最后X这一层便处理完了,直接返回X就可以了。
前序遍历: 3 | 9 | 20 15 7
根 左 右
中序遍历: 9 | 3 | 15 20 7
左 根 右
2.代码实现
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
return rebuild(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
}
private TreeNode rebuild(int[] preorder,int prestart,int preend, int[] inorder,int instart,int inend){
if(prestart>preend||instart>inend) return null;
TreeNode root=new TreeNode(preorder[prestart]);
for(int i=instart;i<=inend;i++){
if(preorder[prestart]==inorder[i]){
root.left=rebuild(preorder,prestart+1,prestart+i-instart,inorder,instart,i-1);
root.right=rebuild(preorder,prestart+i-instart+1,preend,inorder,i+1,inend);
break;
}
}
return root;
}
}
3.复杂度分析
- 时间复杂度:递归栈的深度为二叉树节点的个数,而递归之前,还需要通过循环找到当前根节点的位置,所以复杂度为O(n^2)
- 空间复杂度:需要额外长度为n的递归栈开销,复杂度为O(n)
方法二(递归优化)
1.解题思路
在方法一中,我们需要用循环来找到根节点在中序遍历中的位置,时间开销较大,那有没有什么办法可以改进呢。我们可以考虑用一个哈希表将中序遍历的值和下标做一个映射,那么我们便可以在O(1)时间内找到中序遍历中根节点的位置了。
2.代码实现
class Solution {
HashMap<Integer,Integer> dic=new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for(int i=0;i<inorder.length;i++){
dic.put(inorder[i],i);
}
return rebuild(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
}
private TreeNode rebuild(int[] preorder,int prestart,int preend, int[] inorder,int instart,int inend){
if(prestart>preend||instart>inend) return null;
TreeNode root=new TreeNode(preorder[prestart]);
int i=dic.get(preorder[prestart]);
root.left=rebuild(preorder,prestart+1,prestart+i-instart,inorder,instart,i-1);
root.right=rebuild(preorder,prestart+i-instart+1,preend,inorder,i+1,inend);
return root;
}
}
3.复杂度分析
- 时间复杂度:递归栈的深度为二叉树节点的个数,所以复杂度为O(n)
- 空间复杂度:需要额外长度为n的递归栈开销和长度为n的哈希表,复杂度为O(n)
方法三(迭代法)
1.解题思路
比如有如下一颗二叉树:
3 / \ 9 20 / / \ 8 15 7 / \ 5 10 / 4
- preorder = [3, 9, 8, 5, 4, 10, 20, 15, 7]
- inorder = [4, 5, 8, 10, 9, 3, 15, 20, 7]
观察前序遍历,假设前序遍历中每两个相邻的节点分别为u和v,那么u和v的关系存在两种情况:
- v是u的左孩子
- 如果v对应于前序遍历中标红的节点,那么它和u有什么关系呢,当我们不断将u往其父节点回溯,我们找到v某一个祖先节点(标绿的节点)的右孩子恰好是v
既然直到前序遍历有这么一个规律,那中序遍历有什么用呢,中序遍历可以帮我们找到v对应的那个祖先节点,那回溯的过程怎么实现呢,我们可以通过一个栈来模拟回溯的过程。
2.代码实现
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder == null || preorder.length == 0) {
return null;
}
TreeNode root = new TreeNode(preorder[0]);
Deque<TreeNode> stack = new LinkedList<TreeNode>();
stack.push(root);
int inorderIndex = 0;
for (int i = 1; i < preorder.length; i++) {
int preorderVal = preorder[i];
TreeNode node = stack.peek();
//构建左子树,直到找到中序遍历的首位,即当前最左,不能继续往下走了
if (node.val != inorder[inorderIndex]) {
node.left = new TreeNode(preorderVal);
stack.push(node.left);
} else {
//模拟回溯
while (!stack.isEmpty() && stack.peek().val == inorder[inorderIndex]) {
node = stack.pop();
inorderIndex++;
}
node.right = new TreeNode(preorderVal);
stack.push(node.right);
}
}
return root;
}
}
3.复杂度分析
- 时间复杂度:需要确定树的每一个节点,树的节点个数为n,所以复杂度为O(n)
- 空间复杂度:需要一个栈来存储中间节点,栈的深度最大为树节点个数n,所以复杂度为O(n)
剑指offer全集入口: 请戳这里