剑指Offer算法笔记(Java)重建二叉树

5.重建二叉树

描述
给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。

剑指Offer算法笔记(Java)重建二叉树_第1张图片
提示:
1.vin.length == pre.length
2.pre 和 vin 均无重复元素
3.vin出现的元素均出现在 pre里
4.只需要返回根结点,系统会自动输出整颗树做答案对比
数据范围:n≤ 2000n≤2000,节点的值 -10000 ≤ val ≤ 10000
要求:空间复杂度 O(n),时间复杂度 O(n)

示例1
输入:
[1,2,4,7,3,5,6,8],[4,7,2,1,5,3,8,6]

返回值:
{1,2,3,4,#,5,6,#,7,#,#,8}
说明:
返回根节点,系统会输出整颗二叉树对比结果,重建结果如题面图示

示例2
输入:
[1],[1]

返回值:
{1}

示例3
输入:
[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]

返回值:
{1,2,5,3,4,6,7}

题解

一、递归
对于二叉树的前序遍历,我们知道序列的第一个元素必定是根节点的值,因为序列没有重复的元素,因此中序遍历中可以找到相同的这个元素,而我们又知道中序遍历中根节点将二叉树分成了左右子树两个部分,如下图所示:
剑指Offer算法笔记(Java)重建二叉树_第2张图片
我们可以发现,数字1是根节点,并将二叉树分成了(247)和(3568)两棵子树,而子树的的根也是相应前序序列的首位,比如左子树的根是数字2,右子树的根是数字3,这样我们就可以利用前序遍历序列找子树的根节点,利用中序遍历序列区分每个子树的节点数。

具体做法:

step 1:先根据前序遍历第一个点建立根节点。
step 2:然后遍历中序遍历找到根节点在数组中的位置。
step 3:再按照子树的节点数将两个遍历的序列分割成子数组,将子数组送入函数建立子树。
step 4:直到子树的序列长度为0,结束递归。

import java.util.*;
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] vin) {
        int n = pre.length;
        int m = vin.length;
        //每个遍历都不能为0
        if(n == 0 || m == 0)
            return null;
        //构建根节点
        TreeNode root = new TreeNode(pre[0]);
        for(int i = 0; i < vin.length; i++){
            //找到中序遍历中的前序第一个元素
            if(pre[0] == vin[i]){
                //构建左子树
                root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i + 1), Arrays.copyOfRange(vin, 0, i));
                //构建右子树
                root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i + 1, pre.length), Arrays.copyOfRange(vin, i + 1, vin.length));
                break;
            }
        }
        return root;
    }
}

复杂度分析:

时间复杂度:O(n),其中n为数组长度,即二叉树的节点数,构建每个节点进一次递归,递归中所有的循环加起来一共n次

空间复杂度:O(n),递归栈最大深度不超过n,辅助数组长度也不超过n,重建的二叉树空间属于必要空间,不属于辅助空间
二、栈
用类似非递归前序遍历的方式建立二叉树,利用栈辅助进行非递归,然后依次建立节点。

具体做法:

step 1:首先前序遍历第一个数字依然是根节点,并建立栈辅助遍历。
step 2:然后我们就开始判断,在前序遍历中相邻的两个数字必定是只有两种情况:要么前序后一个是前一个的左节点;要么前序后一个是前一个的右节点或者其祖先的右节点。
step 3:我们可以同时顺序遍历pre和vin两个序列,判断是否是左节点,如果是左节点则不断向左深入,用栈记录祖先,如果不是需要弹出栈回到相应的祖先,然后进入右子树,整个过程类似非递归前序遍历。

Java实现代码:

import java.util.*;
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] vin) {
        int n = pre.length;
        int m = vin.length;
        //每个遍历都不能为0
        if(n == 0 || m == 0) 
            return null;
        Stack<TreeNode> s = new Stack<TreeNode>();
        //首先建立前序第一个即根节点
        TreeNode root = new TreeNode(pre[0]); 
        TreeNode cur = root;
        for(int i = 1, j = 0; i < n; i++){
            //要么旁边这个是它的左节点
            if(cur.val != vin[j]){ 
                cur.left = new TreeNode(pre[i]);
                s.push(cur);
                //要么旁边这个是它的右节点,或者祖先的右节点
                cur = cur.left; 
            }else{
                j++;
                //弹出到符合的祖先
                while(!s.isEmpty() && s.peek().val == vin[j]){
                    cur = s.pop();
                    j++;
                }
                //添加右节点
                cur.right = new TreeNode(pre[i]); 
                cur = cur.right;
            }
        }
        return root;
    }
}

复杂度分析:

时间复杂度:O(n),其中n为数组长度,即二叉树的节点数,遍历一次数组,弹出栈的循环最多进行n次
空间复杂度:O(n),栈空间最大深度为n,重建的二叉树空间属于必要空间,不属于辅助空间

你可能感兴趣的:(java,算法,剑指offer,java,算法)