题目:
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
解法1:递归
/**
* preorder: [1, 2, 4, 5, 3, 6]
* inorder: [4, 2, 5, 1, 6, 3]
* 1.preorder的第一个元素1是根
* 2.搜索中序
* 3.通过中序分割成两部分[4,2,5]和[6,3]
* 4.将前序遍历除根节点外的其余部分分为两部分,和中序遍历一样大。[2,4,5]和[3,6]
* 5.使用preorder = [2, 4, 5] and inorder = [4, 2, 5] 添加左子树
* 6.使用preorder =[3, 6] and inorder = [6, 3] 添加右子树
*
* 第一步:考虑最坏的情况,树退化成了链表,集中在左子树上。此时前序遍历的结果反转过来是中序的结果1234,4321
* 第二步:在中序中查找,根据您如何“拆分”数组,您将可能看到,时间复杂度O(n^2) ,空间复杂度O(n^2)
* 在开始主要工作之前,通过构建一个map,k是值,v是在中序中的索引位置,可以将搜索的运行时降到O(n),我已经看到几种解决方案可以做到这一点。
* 但这是O(n)额外的空间,分裂问题仍然存在。要解决这些问题,可以在前序和中序中使用指针,而不是拆分它们。当你这么做的时候,你也不需要map索引的kv。
*
* 再考虑一下这个例子。不要在中序中找根,而是将数组分成几个部分并在它们上递归,只需在剩余的完整数组上递归,并在中序中遇到根时停止。这就是我上面的解决方案。
* 它将自己的根值root.val作为stopper赋给它的左子树调用,将其父级的stopper作为stopper赋给它的右子树调用。
*/
private int in = 0;
private int pre = 0;
public TreeNode buildTree(int[] preorder, int[] inorder) {
return build(preorder, inorder, Integer.MIN_VALUE);
}
private TreeNode build(int[] preorder, int[] inorder, int stop) {
if (pre >= preorder.length) return null;
if (inorder[in] == stop) {
in++;
return null;
}
TreeNode node = new TreeNode(preorder[pre++]);
node.left = build(preorder, inorder, node.val);
node.right = build(preorder, inorder, stop);
return node;
}
时间复杂度:On
空间复杂度:O1
解法2:递归(光头哥说的On^2的情况)
/**
* 思路:
* 1
* 2 3
* 4 5 6 7
*
* 前序:1245 367
* 中序:425 1 637
*
* 根据中序遍历我们可以知道根节点的位置,以及根节点的左子树和右子树各有多少个节点:425 1 637
* 在知道左子树和右子树个数的情况下,我们就能在前序遍历中找到左子树和右子树的起点和终点
* 之后递归的设置左右子树
*
* 左子树
* 前序:根+1 到 根+左子树个数+1。--1245
* 中序:i_start 到 根的位置。--425 1
*
* 右子树
* 前序:根+移动的位置+1 到 结束的位置。--367
* 中序:根+1 到 结束的位置--637
*/
public TreeNode buildTree(int[] preorder, int[] inorder) {
return recursive(preorder,0, preorder.length,inorder,0,inorder.length);
}
private TreeNode recursive(int[] preorder, int p_start, int p_end, int[] inorder, int in_start, int in_end) {
if (p_start==p_end)return null;
TreeNode root = new TreeNode(preorder[p_start]);
int in_root_index=0;
for (int i=in_start;i<in_end;i++){
if (inorder[i]==preorder[p_start]){
in_root_index=i;
break;
}
}
int leftNum=in_root_index-in_start;
root.left=recursive(preorder,p_start+1,p_start+leftNum+1,inorder,in_start,in_root_index);
root.right=recursive(preorder,p_start+leftNum+1,p_end,inorder,in_root_index+1,in_end);
return root;
}
时间复杂度:On^2
空间复杂度:O1
解法3:递归(光头哥说的用map降到On的情况)
/**
* 思路:
* 本题有一个前提条件,假设树中没有重复元素,所以可以采用map来存储中序遍历的值
* 前序的p_start就是根,从中序中就可以划分左右子树
* 记录中序中移动的位置,之后就可以划分了
*/
public TreeNode buildTree(int[] preorder, int[] inorder) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int i=0;i<inorder.length;i++){
map.put(inorder[i],i);
}
return recursive(preorder,0,preorder.length,inorder,0,inorder.length,map);
}
private TreeNode recursive(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end, HashMap<Integer, Integer> map) {
if (p_start==p_end)return null;
int root_val = preorder[p_start];
TreeNode root = new TreeNode(root_val);
int i_root_index = map.get(root_val);
int leftNum = i_root_index - i_start;
root.left=recursive(preorder,p_start+1,p_start+leftNum+1,inorder,i_start,i_root_index,map);
root.right=recursive(preorder,p_start+leftNum+1,p_end,inorder,i_root_index+1,i_end,map);
return root;
}
时间复杂度:On
空间复杂度:On