题目:
输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
题解:
售后线了解一下二叉树的前序和中序,前序指的是根左右的方式进行遍历,中序是指左根右的方式进行遍历,根据二叉树的前序和中序遍历可以构建一个唯一的二叉树。
假设某二叉树有如下前序和中序遍历,那么我们如何根据前序和中序遍历结果构建唯一二叉树呢?
根据前序遍历的结果,可以得到根节点28,再回到中序遍历,可以得到左子树和右子树:
可以看见,根节点28确定下来后,能够轻易得到此根节点的左子树和右子树,再将左子树和右子树分别拆开,以左子树为例,根据左子树的前序和中序遍历结果,可以对左子树进行进一步的划分。
上图的褐色部分是以28为根节点的左子树的前序和中序遍历结果。和刚开始划分的步骤一样,首先根据前序遍历结果确定根节点,再根据中序遍历结果划分左右子树。此时的根节点为16,根据中序遍历结果可以得到:以16为根节点的二叉树中,其左子树为13,右子树为22.最后,再重复上述步骤,根据前序遍历结果找根节点,然后根据中序遍历结果找左右子树,一直划分到只剩一个结点即可。
最终可以得到如下二叉树:
28
/ \
16 30
/ \ / \
13 22 29 43
思想有了,代码如何实现是一个问题,首先可以确定的是,这是一个分治的过程,能够通过地递归解决。
先说第一种方法,将前序和中序不断拆分,拆分成新的数组,这样前序和中序数组就很容易构建。比如,遍历结果到了这一步,将这个数组再拆分成新的前序和中序数组。
首先,要明确的就是二叉树的前序遍历长度和中序遍历长度一定是一样大的,只要我们能够得到前序和中序遍历中根节点的下标就可以确定子树的边界范围和子树的根节点。比如28在前序中的下标为0(这是一定的),28在中序中的下标为3,那么左子树的长度就是3-0=3,接下来,我们思考如何得到以28为根节点的左子树的前序和中序遍历。先看左子树的前序遍历,其左边界就是0+1=1,0是28在前序中的下标位置,左边界是28在中序遍历中的下标3,这就确定了左子树的边界[1,3];再看左子树的中序遍历边界,左边界的位置一定为0(因为这是在不停划分新的数组),右边界是28在中序中的位置-1。右子树的前序遍历:左边界,28在中序中的下标+1,右边界,新数组的长度-1(因为这是在不停划分新的数组);右子树的中序遍历:左边界,28在中序中的下标+1,右边界,新数组的长度-1。不停的划分新的数组,一直到数组为空。
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length == 0) return null;//前序遍历和中序遍历的数组长度一样,所以只需要判断一个
TreeNode root = new TreeNode(preorder[0]);//前序遍历的第一个数字一定是根节点
int index = findIndex(root.val,inorder);
root.left = buildTree(Arrays.copyOfRange(preorder,1,index + 1),Arrays.copyOfRange(inorder,0,index));//构建左子树
root.right = buildTree(Arrays.copyOfRange(preorder,index + 1,preorder.length),Arrays.copyOfRange(inorder,index + 1,inorder.length));//构建右子树
return root;
}
//查找前序中根节点在中序的位置
public static int findIndex(int rootValue, int[] inorder){
for (int i = 0; i < inorder.length; i++) {
if(inorder[i] == rootValue) return i;
}
return 0;
}
这种方法需要在每次递归时,都需要查找一次中序下标,还要不停copy新的数组,时间复杂度和空间复杂度都很高。这就有两个优化的地方,第一、不copy新的数组,直接过原始数组确定子树的左右边界,第二、有了固定数组,就可以构建hash表,提高查找速度。
public static TreeNode buildTree(int[] preorder, int[] inorder) {
HashMap<Integer,Integer> hashMap = new HashMap<>();
for (int i = 0; i < inorder.length; i++) {
hashMap.put(inorder[i],i);
}
return recur(preorder,0,0, inorder.length - 1,hashMap);
}
/**
*
* @param preorder 前序遍历
* @param preRoot 前序遍历中根节点的位置
* @param inLeft 中序遍历中的左边界
* @param inRight 中序遍历中的右边界
* @param map 记录中序每个数值位置的哈希表
* @return
*/
public static TreeNode recur(int[] preorder,int preRoot,int inLeft,int inRight,HashMap<Integer,Integer> map){
if(inLeft > inRight) return null;
int rootIndex = map.get(preorder[preRoot]);
TreeNode root = new TreeNode(preorder[preRoot]);//构建根节点
/**
* 前序中根节点的位置 = 前序根节点的位置+1(和前面说的方法雷同)
* 在构建左子树的过程中,左边界是不变的
* 右边界 = 中序中根节点的位置-1
*/
root.left = recur(preorder,preRoot + 1,inLeft,rootIndex - 1,map);
/**
* 前序中根根节点的位置 = 前序根节点的位置 + 左子树长度 + 1
* 左子树的长度 = 根节点位置 - 中序左边界位置
* 左边界 = 中序根节点位置 + 1
* 在构建右子树的过程中,右边界不变
*/
root.right = recur(preorder,preRoot + (rootIndex - inLeft) + 1,rootIndex + 1,inRight,map);
return root;
}