# 力扣解题-106. 从中序与后序遍历序列构造二叉树

0 阅读7分钟

力扣解题-106. 从中序与后序遍历序列构造二叉树

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗二叉树。

示例 1:

image.png

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]

输出:[3,9,20,null,null,15,7]

示例 2:

输入:inorder = [-1], postorder = [-1]

输出:[-1]

提示: 1 <= inorder.length <= 3000

postorder.length == inorder.length

-3000 <= inorder[i], postorder[i] <= 3000

inorder 和 postorder 都由 不同 的值组成

postorder 中每一个值都在 inorder 中

inorder 保证是树的中序遍历

postorder 保证是树的后序遍历

Related Topics

树、数组、哈希表、分治、二叉树


示例解答

解题思路

核心方法:分治递归+哈希表优化,利用后序遍历“左→右→根”和中序遍历“左→根→右”的核心特性,通过分治思想将构建整棵树拆解为“构建根节点→递归构建左右子树”,哈希表用于快速定位中序数组中根节点的位置,将时间复杂度从O(n²)优化至O(n),是本题的最优解法。

核心逻辑拆解

构造二叉树的关键在于“双序列联动找边界”,核心三步:

  1. 找根节点:后序遍历的最后一个元素一定是当前子树的根节点(后序遍历的“根”在末尾);
  2. 分左右子树:在中序数组中找到根节点的位置,其左侧所有元素是左子树节点,右侧所有元素是右子树节点;
  3. 递归构建:根据中序数组划分的左右子树节点数量,确定后序数组中左右子树的区间,递归构建左右子树并挂载到根节点。
详细步骤拆解
1. 预处理:哈希表映射中序值与索引
  • 遍历中序数组inorder,将每个节点值作为key、索引作为value存入inMap
  • 核心作用:将“在中序数组中查找根节点位置”的操作从O(n)(遍历)降至O(1)(哈希表查询),这是优化时间复杂度的关键。
2. 递归函数参数说明

build(inorder, inStart, inEnd, postorder, postStart, postEnd, inMap)的参数含义:

  • inStart/inEnd:当前子树在中序数组中的起止索引;
  • postStart/postEnd:当前子树在后序数组中的起止索引;
  • inMap:中序值→索引的哈希表,避免重复遍历。
3. 递归终止条件

inStart > inEndpostStart > postEnd时,说明当前子树无节点(空区间),返回null,这是递归的基线条件,天然处理“叶子节点的子节点为空”的场景。

4. 单轮递归核心操作(以示例1为例)

输入:inorder = [9,3,15,20,7]postorder = [9,15,7,20,3]

  • 步骤1:确定根节点:后序末尾值postorder[4] = 3是整棵树的根;
  • 步骤2:定位中序根节点:通过inMap找到3在中序的索引inRootIndex = 1
  • 步骤3:计算左子树节点数leftSize = 1 - 0 = 1(中序根左侧有1个节点);
  • 步骤4:递归构建左子树
    • 中序区间:[0, 0](仅9),后序区间:[0, 0](仅9);
  • 步骤5:递归构建右子树
    • 中序区间:[2, 4](15、20、7),后序区间:[1, 3](15、7、20);
  • 步骤6:挂载并返回:将左右子树挂载到根节点3,返回根节点。
关键区间划分逻辑(核心易错点)
子树类型中序区间后序区间推导依据
左子树[inStart, inRootIndex-1][postStart, postStart + leftSize - 1]左子树节点数=leftSize
右子树[inRootIndex+1, inEnd][postStart + leftSize, postEnd - 1]后序末尾是根,需排除根节点
执行流程可视化(示例1)
递归层级根节点中序区间后序区间左子树节点数操作结果
13[0,4][0,4]1构建根3,左9、右20
29[0,0][0,0]0构建根9,无左右子树
220[2,4][1,3]1构建根20,左15、右7
315[2,2][1,1]0构建根15,无左右子树
37[4,4][2,2]0构建根7,无左右子树
性能说明
  • 时间复杂度:O(n)
    • 哈希表构建:O(n);
    • 递归构建:每个节点仅被处理一次,O(n);
    • 无哈希表优化时为O(n²)(每次找根需遍历中序数组)。
  • 空间复杂度:O(n)
    • 哈希表存储n个节点:O(n);
    • 递归栈深度:平衡树O(logn),斜树O(n);
    • 总复杂度由哈希表主导,为O(n)。
  • 优势
    1. 分治思想贴合二叉树的递归结构,逻辑清晰易维护;
    2. 哈希表优化后效率最优,适合题目中n≤3000的场景;
    3. 天然处理单节点、空树等边界条件。
public TreeNode buildTree(int[] inorder, int[] postorder) {
        if (inorder == null || postorder == null || inorder.length == 0) {
            return null;
        }

        // 构建中序值到索引的映射
        Map<Integer, Integer> inMap = new HashMap<>();
        for (int i = 0; i < inorder.length; i++) {
            inMap.put(inorder[i], i);
        }

        return build(inorder, 0, inorder.length - 1,
                     postorder, 0, postorder.length - 1,
                     inMap);
    }

    private TreeNode build(int[] inorder, int inStart, int inEnd,
                           int[] postorder, int postStart, int postEnd,
                           Map<Integer, Integer> inMap) {
        // 递归终止条件
        if (inStart > inEnd || postStart > postEnd) {
            return null;
        }

        // 1. 后序的最后一个元素是当前子树的根
        int rootVal = postorder[postEnd];
        TreeNode root = new TreeNode(rootVal);

        // 2. 在中序中找到根的位置
        int inRootIndex = inMap.get(rootVal);

        // 3. 计算左子树的节点数量
        int leftSize = inRootIndex - inStart;

        // 4. 递归构建左子树
        //    中序: [inStart, inRootIndex - 1]
        //    后序: [postStart, postStart + leftSize - 1]
        root.left = build(inorder, inStart, inRootIndex - 1,
                          postorder, postStart, postStart + leftSize - 1,
                          inMap);

        // 5. 递归构建右子树
        //    中序: [inRootIndex + 1, inEnd]
        //    后序: [postStart + leftSize, postEnd - 1]
        root.right = build(inorder, inRootIndex + 1, inEnd,
                           postorder, postStart + leftSize, postEnd - 1,
                           inMap);

        return root;
    }

拓展解法:迭代法(思路拓展)

核心方法:栈模拟递归+逆序遍历,利用后序逆序(根→右→左)与中序逆序(右→根→左)的特性,通过栈记录待构建左子树的节点,迭代完成二叉树构建,空间复杂度仍为O(n),但避免了递归栈的调用。

代码实现
public TreeNode buildTree(int[] inorder, int[] postorder) {
    if (postorder == null || postorder.length == 0) {
        return null;
    }
    Stack<TreeNode> stack = new Stack<>();
    // 后序最后一个元素是根节点
    TreeNode root = new TreeNode(postorder[postorder.length - 1]);
    stack.push(root);
    int postIdx = postorder.length - 2; // 后序指针从倒数第二个开始
    int inIdx = inorder.length - 1;     // 中序指针从最后一个开始

    while (postIdx >= 0) {
        TreeNode curr = stack.peek();
        // 情况1:当前节点是栈顶的右子树
        if (curr.val != inorder[inIdx]) {
            TreeNode right = new TreeNode(postorder[postIdx]);
            curr.right = right;
            stack.push(right);
            postIdx--;
        } 
        // 情况2:找到栈顶节点的左子树
        else {
            // 弹出已构建完右子树的节点
            while (!stack.isEmpty() && stack.peek().val == inorder[inIdx]) {
                curr = stack.pop();
                inIdx--;
            }
            // 构建左子树
            TreeNode left = new TreeNode(postorder[postIdx]);
            curr.left = left;
            stack.push(left);
            postIdx--;
        }
    }
    return root;
}
核心逻辑说明
  1. 初始化:根节点入栈,后序指针从倒数第二个元素逆序遍历,中序指针从末尾逆序遍历;
  2. 构建右子树:若栈顶节点值≠中序指针值,当前后序值是栈顶的右子节点,入栈;
  3. 切换到左子树:若栈顶节点值=中序指针值,说明栈顶的右子树已构建完成,弹出栈顶并左移中序指针,直到找到左子树的父节点;
  4. 构建左子树:将当前后序值作为父节点的左子节点,入栈。
性能说明
  • 时间复杂度:O(n)(每个节点仅入栈/出栈一次);
  • 空间复杂度:O(n)(栈最多存储n个节点);
  • 适用场景:适合需要避免递归栈溢出的极端场景(如斜树),但逻辑较递归法复杂,可读性较低。

总结

  1. 分治递归+哈希表(最优解):O(n)时间+O(n)空间,逻辑清晰、效率最优,工程中首选;
  2. 迭代法(栈模拟):O(n)时间+O(n)空间,非递归实现,适合拓展思路;
  3. 核心关键点
    • 后序末尾是根,中序根的左右划分左右子树;
    • 哈希表是优化时间复杂度的核心,避免重复遍历中序数组;
    • 后序区间划分是易错点,左子树[postStart, postStart+leftSize-1],右子树[postStart+leftSize, postEnd-1]