剑指Offer_#7_重建二叉树

剑指Offer_#7_重建二叉树

Contents

题目

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]

返回如下的二叉树:

    3
   / 
  9  20
    /  
   15   7

限制:
0 <= 节点个数 <= 5000

思路分析

整体思路

引用题解区liweiwei大佬的一个图解 题解链接

这个图片很清晰地展示了本题的思路。
整体的思路如下:

  1. 找到当前树的根节点。根节点一定是前序遍历序列的第一个,即上图中的1。
  2. 找到根节点在中序遍历序列当中的位置,即上图中pivot指向的位置。
  3. 根据中序遍历中根节点的位置,可以将整个inorder数组划分为左子树部分和右子树部分,即绿色框和红色框部分。
  4. 根据中序遍历中左子树部分的个数,反过来又可以把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;

递推过程

  1. 构建当前子树的根节点root,root的值是preorder[preL]
  2. 在中序遍历序列inorder中寻找根节点root的值所在的位置
    • 方法1:提前构建一个HashMap,保存键值对<inorder[i],i>,可以直接查找到
    • 方法2:遍历inorder数组,找到root的值
  3. 根据上面的图解,我们可以划分出root的左右子树在两个序列里的范围。得到root的左右子树的preL,preR,inL,inR。调用递归子函数,构造出root的左右子树。

返回值(回溯)
返回当前构建的root子树,成为更高一层递归函数中的左右子树。

其他细节

特殊输入

  • preorder/inorder是null
  • preorder/inorder长度为0
  • preorderinorder长度不同

以上情况都无法重建出一个二叉树,返回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)
原文地址:https://www.cnblogs.com/Howfars/p/13411821.html