题目描述

给定某二叉树的前序遍历和中序遍历,请重建出该二叉树并返回它的头结点。

方法一: 递归

解题思路

前序遍历的第一个节点就是这颗树的根节点。

在中序遍历中,找到这个根节点,这个根节点的左边元素就是它的左子树上的元素,这个根节点的右边元素就是它的右子树上的元素。

把范围划分好,直到范围上只有一个元素直接生成返回即可。

需要找到根节点在中序遍历上的下标,才能知道它的左子树上有多少个元素,右子树上有多少个元素。为了方便找到下标,可以用 Map 结构来存储 中序遍历数组上 所有元素和其下标 的对应关系。


以下是以 前序遍历为:[8,3,6,7,9] , 中序遍历为:[3,6,8,9,7] 的演示图:

图片说明

复杂度分析

时间复杂度:O(N*logN)

需要挨个递归遍历每个元素,总共有 N 个元素;
另外在遍历每个元素的时候,用到了 HashMap 的查找操作,虽然可以认为他的查询操作一般情况下是 O(1) 级别的时间复杂度。但底层可能是链表存储,这样的查询操作的时间复杂度为 O(N);也可能是红黑树存储,这样的查询操作的时间复杂度为 O(logN);(采用哪种存储结构和存储的数量有关)
而这里 Map 的结果是有序的,数据量为 N 也不好确定具体是多少,所以最后的时间复杂度为:O(N*logN)。

空间复杂度:O(N)

重建整颗二叉树需要建立每一个节点,总共有 N 个节点,所以空间复杂度为 O(N)

代码示例

import java.util.*;
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
        // 判断特殊情况
        if (pre == null || pre.length == 0 || in == null || in.length == 0) {
            return null;
        }

        // key : 中序遍历元素的值;  
        // value : 中序遍历元素的值 对应的 下标
        // 可以以O(1)的时间复杂度找到元素在中序遍历中对应的下标
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < in.length; i++) {
            map.put(in[i], i);
        }

        // 返回在 pre[0...pre.length-1] , in[0, in.length-1] 范围上建立二叉树的根节点
        return process(pre, 0, pre.length - 1,
                0, in.length - 1,
                map);

    }


    /**
     * @param pre 前序遍历的数组
     * @param pL  使用到的 前序遍历的数组 的左边界,闭区间
     * @param pR  使用到的 前序遍历的数组 的右边界,闭区间
     * @param inL 使用到的 中序遍历的数组 的左边界,闭区间
     * @param inR 使用到的 中序遍历的数组 的右边界,闭区间
     * @param map 方便找到元素在中序遍历中对应的下标 需要的 数据结构
     * @return 在使用到的 前序遍历数组、中序遍历数组中;生成该范围内的二叉树,返回这个二叉树的 根节点
     */
    public TreeNode process(int[] pre, int pL, int pR,
                            int inL, int inR,
                            HashMap<Integer, Integer> map) {
        // 越界情况,没有二叉树存在,直接返回 null
        if (pL > pR || inL > inR) {
            return null;
        }
        // 只有一个元素,就是根节点,直接返回
        if (pL == pR || inL == inR) {
            return new TreeNode(pre[pL]);
        }
        // 前序遍历的第一个元素就是 根节点
        TreeNode root = new TreeNode(pre[pL]);
        // 找到 根节点在 中序遍历数组里的 下标
        int inIndex = map.get(pre[pL]);
        // 在有效的中序遍历范围里, 根节点左边的元素 都是属于 根节点的左子树
        int leftTreeSize = inIndex - inL;
        // 在左子树的有效范围里,生成左子树
        root.left = process(pre, pL + 1, pL + leftTreeSize,
                inL, inIndex - 1,
                map);
        // 在右子树的有效范围里,生成右子树
        root.right = process(pre, pL + leftTreeSize + 1, pR,
                inIndex + 1, inR,
                map);
        return root;
    } 

}

方法二: 用栈

解题思路

虽然递归方法都可以改写成为非递归的方法实现,但还是要具体问题具体分析。

就该题来说,先了解前序遍历和中序遍历的特点:

前序遍历: 顺序就是所有第一次访问到根节点时的顺序,

如果一个元素有左子树,那么下一个元素的值就是它的左子树的值;

如果没有左子树,就看之前访问的哪个元素有右子树,那么下一个元素的值就是“之前访问有右子树的元素”的那个右子树的值。

中序遍历:顺序就是所有第二次访问到根节点时的顺序。

我们可以结合中序遍历的结果,进而找到具体的哪个元素有右子树,具体思路参照代码流程及注释。

所以,整个流程就是遍历一遍前序遍历的数组,生成相对应的左子树;再结合中序遍历,生成相对应的右子树。


以下是以 前序遍历为:[8,3,6,7,9] , 中序遍历为:[3,6,8,9,7] 的演示图:

图片说明

复杂度分析

时间复杂度: O(N)

整个的大流程就是一个 for 循环遍历前序数组,虽然里面有一个 while 循环,但这个 while 循环极端情况下只会发生在前序遍历的某一个元素上,并不是所有元素都会进入 while 循环中,所有时间复杂度为 O(N)。

空间复杂度:O(N)

重建整颗二叉树需要建立每一个节点,总共有 N 个节点,所以空间复杂度为 O(N)

示例代码

import java.util.*;
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        // 判断特殊情况
        if (pre == null || pre.length == 0 || in == null || in.length == 0) {
            return null;
        }

        Stack<TreeNode> stack = new Stack<>();
        int preIndex = 0;
        int inIndex = 0;
        // 整颗树的根节点
        TreeNode root = new TreeNode(pre[preIndex]);
        stack.push(root);

        // 遍历 前序遍历的数组, 生成对应的根节点
        for (preIndex = 1; preIndex < pre.length; preIndex++) {
            TreeNode node = stack.peek();
            // 之前压栈的元素 不等于 中序遍历数组中开头的数,
            // 即 之前压栈的元素 有左子树
            if (node.val != in[inIndex]) {
                // 再生成相应的左子树并压栈
                node.left = new TreeNode(pre[preIndex]);
                stack.push(node.left);
            } else {
                // 之前压栈的元素的值 等于 中序遍历数组中开头的数,
                // 即 之前压栈的元素 没有左子树; 继续找那个元素有右子树
                while (!stack.isEmpty() && stack.peek().val == in[inIndex] ) {
                    node = stack.pop();
                    inIndex++;
                }
                // 再生成相应的右子树并压栈
                node.right = new TreeNode(pre[preIndex]);
                stack.push(node.right);
            }
        }

        // 返回整棵树的根节点
        return root;
    } 
}