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

一、题目描述

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

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

例如,给出

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

返回如下的二叉树:

【leetcode.106】从中序与后序遍历序列构造二叉树_第1张图片


二、思路

思路来源于图解构造二叉树之中序+后序,画得图是真心棒

前提

解决此问题的关键在于要很熟悉树的各种遍历次序代表的什么,最好能够将图画出来。本题解带你先进行中序遍历和后续遍历二叉树,然后再根据遍历结果将二叉树进行还原。

首先,来一棵树

【leetcode.106】从中序与后序遍历序列构造二叉树_第2张图片

然后再看树的遍历结果

【leetcode.106】从中序与后序遍历序列构造二叉树_第3张图片

根据中序和后序遍历结果还原二叉树
中序遍历和后续遍历的特性
首先来看题目给出的两个已知条件中序遍历序列和后序遍历序列 根据这两种遍历的特性我们可以得出两个结论

  1. 在后序遍历序列中,最后一个元素为树的根节点
  2. 在中序遍历序列中,根节点的左边为左子树,根节点的右边为右子树

如下图所示

【leetcode.106】从中序与后序遍历序列构造二叉树_第4张图片

树的还原过程描述
根据中序遍历和后续遍历的特性我们进行树的还原过程分析

  1. 首先在后序遍历序列中找到根节点(最后一个元素)
  2. 根据根节点在中序遍历序列中找到根节点的位置
  3. 根据根节点的位置将中序遍历序列分为左子树和右子树
  4. 根据根节点的位置确定左子树和右子树在中序数组和后续数组中的左右边界位置
  5. 递归构造左子树和右子树
  6. 返回根节点结束

树的还原过程变量定义
需要定义几个变量帮助我们进行树的还原

  1. HashMap memo 需要一个哈希表来保存中序遍历序列中,元素和索引的位置关系.因为从后序序列中拿到根节点后,要在中序序列中查找对应的位置,从而将数组分为左子树和右子树
  2. int ri 根节点在中序遍历数组中的索引位置
  3. 中序遍历数组的两个位置标记 [is, ie],is是起始位置,ie是结束位置
  4. 后序遍历数组的两个位置标记 [ps, pe] ps是起始位置,pe是结束位置

位置关系的计算
在找到根节点位置以后,我们要确定下一轮中,左子树和右子树在中序数组和后续数组中的左右边界的位置。

  1. 左子树-中序数组 is = is, ie = ri - 1
  2. 左子树-后序数组 ps = ps, pe = ps + ri - is - 1 (pe计算过程解释,后续数组的起始位置加上左子树长度-1 就是后后序数组结束位置了,左子树的长度 = 根节点索引-左子树)
  3. 右子树-中序数组 is = ri + 1, ie = ie
  4. 右子树-后序数组 ps = ps + ri - is, pe - 1

听不明白没关系,看图就对了,计算图示如下

【leetcode.106】从中序与后序遍历序列构造二叉树_第5张图片

树的还原过程

 


三、代码实现

修改了原作者对变量的命名,现在小学二年级的弟弟都看懂了。

将中序遍历和后序遍历的数组抽离到类成员级别,可以有效的避免数组在多次递归中的复制操作,提升运行效率。

    public class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int x) {
            val = x;
        }
    }

    //保存中序遍历数值与下标的映射关系
    Map inOrderMap = new HashMap<>();
    //后序遍历数组
    int[] postOrderArray;

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        for (int i = 0; i < inorder.length; i++) {
            inOrderMap.put(inorder[i], i);
        }
        postOrderArray = postorder;

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

    }

    /**
     * @param inOrderStart   中序遍历下标开始值
     * @param inOrderEnd     中序遍历下标结束值
     * @param postOrderStart 后序遍历下标开始值
     * @param postOrderEnd   后序遍历下标结束值
     * @return
     */
    public TreeNode build(int inOrderStart, int inOrderEnd, int postOrderStart, int postOrderEnd) {
        if (inOrderStart > inOrderEnd || postOrderStart > postOrderEnd) {
            return null;
        }
        //获取根节点
        int rootValue = postOrderArray[postOrderEnd];
        //根节点在中序遍历中的索引
        int rootIndex = inOrderMap.get(rootValue);
        //构造根节点
        TreeNode root = new TreeNode(rootValue);
        root.left = build(inOrderStart, rootIndex - 1, postOrderStart, postOrderStart + rootIndex - inOrderStart - 1);
        root.right = build(rootIndex + 1, inOrderEnd, postOrderStart + rootIndex - inOrderStart, postOrderEnd - 1);
        return root;
    }

该方案需要访问长度为n的map与数组,且节点总数目也为n,因此时间复杂度为O(n)

空间复杂度,这边使用了长度为n的map与数组,且递归的平均深度(即树的高度)小于n,因此,空间复杂度为O(n)。

提交答案:

【leetcode.106】从中序与后序遍历序列构造二叉树_第6张图片

你可能感兴趣的:(LeetCode,数据结构,二叉树)