重建二叉树——递归实现(Java)
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
题目来源:牛客网-剑指Offer编程题-重建二叉树
解题思路
- 前序遍历的第一个结点就是当前的根结点;
- 根据根结点在中序遍历的位置可以在中序遍历中切割出左右子树的中序遍历序列;
- 根据左右子树的中序遍历序列,可以将前序遍历序列也切割出左右子树的前序遍历序列;
- 重复这个过程,将左右子树的前序遍历序列的第一个元素作为当前根节点左右结点,再返回当前根节点;
如例题所示:
- 前序遍历序列(用pre[]指代){1,2,4,7,3,5,6,8}的第一个结点 pre[0] 为当前根结点;
- pre[0] 在中序遍历序列(用in[]指代){4,7,2,1,5,3,8,6}的位置为3,切割这个中序序列,得到 root.left 的中序遍历序列{4,7,2}和 root.right 的中序遍历序列{5,3,8,6};
- root.left 的中序遍历序列有 3 个元素,前序遍历中除第一个元素以外的前 3 个元素就是 root.left 的前序遍历序列的元素;由于前序遍历的顺序是中左右,所以除了第一个元素是根结点外,接下来的 3 个元素就可以切割出来作为 root.left 的前序遍历序列,即{2,4,7},剩下部分为 root.right 的前序遍历序列{3,5,6,8};
- 重复步骤1-3,将左右子树的前序遍历序列的第一个元素作为当前根节点左右结点,返回当前根节点 pre[0] 。
代码
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
import java.util.Arrays;
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if(pre.length==0||in.length==0)
return null;
//拿前序遍历序列的第一个元素作为根结点
TreeNode root = new TreeNode(pre[0]);
for(int i = 0;i<in.length;i++){
//找到根结点在中序遍历序列中的位置
if(in[i]==pre[0]){
//根据左子树的前序遍历序列和中序遍历序列递归调用reConstructBinaryTree方法得到左子树的根结点
root.left = reConstructBinaryTree(copyArray(pre,1,i+1),copyArray(in,0,i));
//同上
root.right = reConstructBinaryTree(copyArray(pre,i+1,pre.length),copyArray(in,i+1,in.length));
//退出循环
break;
}
}
//返回根节点
return root;
}
/**
* 拷贝数组 左闭右开
* i 拷贝数组的左边界
* j 拷贝数组的右边界
**/
public static int[] copyArray(int[] arr,int i,int j) {
if (arr == null||arr.length<i||arr.length<j) {
return null;
}
int[] res = new int[j-i];
for (int num = i; num < j; num++) {
res[num-i] = arr[num];
}
return res;
}
}
缺点
由于分割数组采用的方式是复制数组,在递归过程中反复调用了该方法,导致时间复杂度和空间复杂度太大 。
改进方法
因为时间空间复杂度的增加都是因为复制数组导致的,所以需要对其进行优化。
从目的出发,复制数组是为了划分出左子树、根节点和右子树。我们也知道了左右子树在数组中的位置,那么我们就可以通过传递边界坐标来完成对左右子树的划分。
改进后代码
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
import java.util.HashMap;
public class Solution {
HashMap<Integer,Integer> map ;
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
// 将中序遍历序列的值和下标组成键值对进行存储
map = getIndex(in);
return process(pre,in,0,pre.length-1,0);
}
/**
* 返回当前根结点
* @param pre 前序遍历序列
* @param in 中序序遍历序列
* @param pleft 当前根结点的前序遍历序列的左边界
* @param pright 当前根结点的前序遍历序列的右边界
* @param ileft 当前根结点的中序遍历序列的左边界
* @return
*/
public TreeNode process(int[] pre,int[] in, int pleft,int pright,int ileft){
if (pleft>pright){
return null;
}
//inIndex:当前根节点在中序遍历序列中的下标
int inIndex = map.get(pre[pleft]);
//当前根节点的左子树的Size
int leftTreeSize = inIndex - ileft;
TreeNode root = new TreeNode(pre[pleft]);
root.left = process(pre,in,pleft+1,pleft+leftTreeSize,ileft);
root.right = process(pre,in,pleft+leftTreeSize+1,pright,inIndex+1);
return root;
}
public HashMap<Integer,Integer> getIndex(int[] in){
HashMap<Integer,Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < in.length; i++) {
map.put(in[i],i);
}
return map;
}
}