zoukankan      html  css  js  c++  java
  • 剑指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)
  • 相关阅读:
    JDK各版本新特性总结
    Java MyBatis3(3)分页插件PageHelper
    Kubernetes--K8S
    MySQL 查看表结构
    查看mysql库大小,表大小,索引大小
    Linux服务器安装Oracle服务端总结
    怎样玩转千万级别的数据
    Markdown 新手指南
    深入理解Java:注解(Annotation)基本概念
    深入理解Java:注解(Annotation)自定义注解入门
  • 原文地址:https://www.cnblogs.com/Howfars/p/13411821.html
Copyright © 2011-2022 走看看