剑指Offer_#7_重建二叉树
Contents
题目
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/
9 20
/
15 7
限制:
0 <= 节点个数 <= 5000
思路分析
整体思路
引用题解区liweiwei大佬的一个图解 题解链接
这个图片很清晰地展示了本题的思路。
整体的思路如下:
- 找到当前树的根节点。根节点一定是前序遍历序列的第一个,即上图中的1。
- 找到根节点在中序遍历序列当中的位置,即上图中pivot指向的位置。
- 根据中序遍历中根节点的位置,可以将整个
inorder
数组划分为左子树部分和右子树部分,即绿色框和红色框部分。 - 根据中序遍历中左子树部分的个数,反过来又可以把
preorder
数组划分为左子树部分和右子树部分,即绿色框和红色框部分。
以上的步骤将前序遍历序列和中序遍历序列划分成为3部分
- 根节点
- 左子树
- 右子树
但是这还不足以重构整个二叉树,因为这是个递归问题,还需要解决递归子问题
- 左子树部分还可以划分为左子树的左子树,左子树的右子树
- 右子树部分还可以划分为右子树的左子树,右子树的右子树
我们需要编写递归函数来实现上述逻辑。
递归函数
递归参数(函数签名)
TreeNode recur(int preL,int preR,int inL,int inR)
preL
,preR
表示当前子树在前序遍历序列preorder当中的左右边界inL
,inR
表示当前子树在中序遍历序列inorder当中的左右边界
递归终止条件
如果左边界大于右边界,表示当前的子树没有任何节点,是null
if(preL > preR || inL > inR) return null;
递推过程
- 构建当前子树的根节点
root
,root
的值是preorder[preL]
- 在中序遍历序列
inorder
中寻找根节点root
的值所在的位置- 方法1:提前构建一个
HashMap
,保存键值对<inorder[i],i>
,可以直接查找到 - 方法2:遍历
inorder
数组,找到root
的值
- 方法1:提前构建一个
- 根据上面的图解,我们可以划分出
root
的左右子树在两个序列里的范围。得到root
的左右子树的preL
,preR
,inL
,inR
。调用递归子函数,构造出root
的左右子树。
返回值(回溯)
返回当前构建的root
子树,成为更高一层递归函数中的左右子树。
其他细节
特殊输入
preorder
/inorder
是nullpreorder
/inorder
长度为0preorder
和inorder
长度不同
以上情况都无法重建出一个二叉树,返回null
全局变量
hashMap
变量,用于保存键值对<inorder[i],i>
po
变量,保存preorder
数组,避免递归函数参数太多
解答
class Solution {
HashMap<Integer,Integer> map = new HashMap();
//前序遍历序列的全局变量,避免递归函数的参数太多
int[] po;
public TreeNode buildTree(int[] preorder, int[] inorder) {
int preLen = preorder.length;
int inLen = inorder.length;
//特殊输入:null,空数组,两数组长度不同
if(preorder == null || inorder == null || preLen == 0 || inLen == 0 || preLen != inLen)
return null;
po = preorder;
//将中序遍历序列的<inorder[i],i>存入map,以便在inorder数组中快速找到子树根节点的值
for(int i = 0;i <= inorder.length - 1;i++){
map.put(inorder[i],i);
}
//开启递归调用
return recur(0,preLen - 1,0,inLen - 1);
}
//递归函数参数:
//preL,preR表示当前子树在前序遍历序列preorder当中的左右边界
//inL,inR表示当前子树在中序遍历序列inorder当中的左右边界
private TreeNode recur(int preL,int preR,int inL,int inR){
//递归出口条件:左边界大于右边界,含义是当前子树为空
if(preL > preR || inL > inR) return null;
//当前子树的根节点的值就是po[preL]
int pivot = po[preL];
TreeNode root = new TreeNode(pivot);
//利用map,找到当前子树根节点在中序遍历序列inorder中的索引
int pivotIndex = map.get(pivot);
//开启下一级递归调用,将程序阻塞在这里,直到满足递归终止条件
//重点在于递推过程中的4个参数,必须在纸上先画出图,推导出参数,再写代码
root.left = recur(preL + 1,preL + pivotIndex - inL,inL,pivotIndex - 1);
root.right = recur(preL + pivotIndex - inL + 1,preR,pivotIndex + 1,inR);
//回溯,将构造好的子树返回给上一级递归函数
return root;
}
}
复杂度分析
时间复杂度:O(n)
- 初始化
hashmap
,复杂度是O(n)
- 每个递归函数构建一个节点,所以递归函数调用
O(n)
次
空间复杂度:O(n)
hashmap
占用空间O(n)
- 递归调用占用空间
O(n)