LeetCode算法系列 106. 从中序与后序遍历序列构造二叉树

551 阅读4分钟

白菜Java自习室 涵盖核心知识

LeetCode算法系列(Java版) 105. 从前序与中序遍历序列构造二叉树
LeetCode算法系列(Java版) 106. 从中序与后序遍历序列构造二叉树

力扣原题

106. 从中序与后序遍历序列构造二叉树

根据一棵树的中序遍历与后序遍历构造二叉树。

注意: 你可以假设树中没有重复的元素。

例如,给出

中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]

返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

分治法

二叉树相关的很多问题的解决思路都有分治法的思想在里面。

分治法的思想:把原问题拆解成若干个与原问题结构相同但规模更小的子问题,待子问题解决以后,原问题就得以解决。

此题解决问题的方式与LeetCode算法系列 105. 从前序与中序遍历序列构造二叉树类似。

解题思路

首先是一样的问题:为什么有中序遍历和后序遍历的结果,就能定义构造出整个二叉树呢?

先来复习明确中序遍历和后序遍历的含义:

  • 中序遍历的顺序是 左子树->根节点->右子树
  • 后序遍历的顺序是 左子树->右子树->根节点
    注意:中序遍历的总体的 根节点->右子树 和后序遍历总体的 右子树->根节点 存在部分 重叠之处,这些重叠之处就是我们推理寻找规律的关键所在。

我们通过一个更复杂的例子,来清晰的分析解题思路:

中序遍历=[4,2,8,5,9,1,6,10,3,7]
后序遍历=[4,8,9,5,2,10,6,7,3,1]

当我们手里已明确得到中序遍历和后序遍历的结果时,能根据二叉树的特性推理得到以下规律:

  1. 中序遍历和后序遍历都是同一棵树的遍历结果,两个结果中树的节点定义一致

  2. 后序遍历的最后一个数,必然是整个二叉树的根节点,如例子中的1

  3. 如果1是整个二叉树的根节点,中序遍历中的1也是整个二叉树的根节点,他左边[4,2,8,5,9]就是左子树,他右边[6,10,3,7]就是右子树;

  4. 同一个根节点1下,在中序遍历和后序遍历的结果中,根节点的左子树和右子树的节点个数必然一样,中序遍历的左子树[4,2,8,5,9]为5个节点,右子树[6,10,3,7]为4个节点,所以后序遍历的左子树也为5个节点[4,8,9,5,2],右子树也为4个节点[10,6,7,3]

  5. 此时问题已经分治为几个小的相同的问题,显然能作为递归的结构去处理。

  • 首先这个二叉树的根节点为1
  • 根节点1的左子树,中序遍历结果为[4,2,8,5,9],后序遍历结果为[4,8,9,5,2]
  • 根节点1的右子树,中序遍历结果为[6,10,3,7],中序遍历结果为[10,6,7,3]
  1. 如何划分数组的界限,我们可以定义不同的下标(游标)来实现编码。中序遍历左右inLeftinRight,后序遍历左右postLeftpostRight

代码实现

import java.util.HashMap;
import java.util.Map;


public class Solution {

    /**
     * 让 postorder 成为全局变量,以免在递归方法中一直传递
     */
    private int[] postorder;
    private Map<Integer, Integer> hash;

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        int inLen = inorder.length;
        int postLen = postorder.length;

        if (inLen != postLen) {
            throw new RuntimeException("输入错误");
        }

        this.postorder = postorder;
        hash = new HashMap<>();
        for (int i = 0; i < inLen; i++) {
            hash.put(inorder[i], i);
        }

        return buildTree(0, inLen - 1, 0, postLen - 1);
    }

    /**
     * 使用中序遍历序列 inorder 的子区间 [inLeft, inRight]
     * 与后序遍历序列 postorder 的子区间 [postLeft, postRight] 构建二叉树
     *
     * @param inLeft    中序遍历序列的左边界
     * @param inRight   中序遍历序列的右边界
     * @param postLeft  后序遍历序列的左边界
     * @param postRight 后序遍历序列的右边界
     * @return 二叉树的根结点
     */
    private TreeNode buildTree(int inLeft, int inRight, int postLeft, int postRight) {
        if (inLeft > inRight || postLeft > postRight) {
            return null;
        }

        int pivot = postorder[postRight];
        int pivotIndex = hash.get(pivot);
        TreeNode root = new TreeNode(pivot);
        root.left = buildTree(inLeft, pivotIndex - 1, postLeft, postRight - inRight + pivotIndex - 1);
        root.right = buildTree(pivotIndex + 1, inRight, postRight - inRight + pivotIndex, postRight - 1);
        return root;
    }
}

复杂度分析

  • 时间复杂度O(N)O(N),这里 NN 是二叉树的结点个数,二叉树的每个结点都遍历了 2 次,第 1 次是扫描构建哈希表,第 2 次是递归构建二叉树;

  • 空间复杂度O(N)O(N)

LeetCode算法系列(Java版) 105. 从前序与中序遍历序列构造二叉树
LeetCode算法系列(Java版) 106. 从中序与后序遍历序列构造二叉树