这是力扣第 889 题「 根据前序和后序遍历构造二叉树 」,给你输入二叉树的前序和后序遍历结果,让你还原二叉树的结构。

函数签名如下:

TreeNode constructFromPrePost(int[] preorder, int[] postorder);

这道题和前两道题有一个本质的区别:

通过前序中序,或者后序中序遍历结果可以确定唯一一棵原始二叉树,但是通过前序后序遍历结果无法确定唯一的原始二叉树

题目也说了,如果有多种可能的还原结果,你可以返回任意一种。

为什么呢?我们说过,构建二叉树的套路很简单,先找到根节点,然后找到并递归构造左右子树即可。

前两道题,可以通过前序或者后序遍历结果找到根节点,然后根据中序遍历结果确定左右子树(题目说了树中没有 val 相同的节点)。

这道题,你可以确定根节点,但是无法确切的知道左右子树有哪些节点。

举个例子,比如给你这个输入:

preorder = [1,2,3], postorder = [3,2,1]

下面这两棵树都是符合条件的,但显然它们的结构不同:

不过话说回来,用后序遍历和前序遍历结果还原二叉树,解法逻辑上和前两道题差别不大,也是通过控制左右子树的索引来构建:

1、首先把前序遍历结果的第一个元素或者后序遍历结果的最后一个元素确定为根节点的值

2、然后把前序遍历结果的第二个元素作为左子树的根节点的值

3、在后序遍历结果中寻找左子树根节点的值,从而确定了左子树的索引边界,进而确定右子树的索引边界,递归构造左右子树即可

详情见代码。

class Solution {
    // 存储 postorder 中值到索引的映射
    HashMap<Integer, Integer> valToIndex = new HashMap<>();
 
    public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
        for (int i = 0; i < postorder.length; i++) {
            valToIndex.put(postorder[i], i);
        }
        return build(preorder, 0, preorder.length - 1,
                    postorder, 0, postorder.length - 1);
    }
 
    // 定义:根据 preorder[preStart..preEnd] 和 postorder[postStart..postEnd]
    // 构建二叉树,并返回根节点。
    TreeNode build(int[] preorder, int preStart, int preEnd,
                   int[] postorder, int postStart, int postEnd) {
        if (preStart > preEnd) {
            return null;
        }
        if (preStart == preEnd) {
            return new TreeNode(preorder[preStart]);
        }
 
        // root 节点对应的值就是前序遍历数组的第一个元素
        int rootVal = preorder[preStart];
        // root.left 的值是前序遍历第二个元素
        // 通过前序和后序遍历构造二叉树的关键在于通过左子树的根节点
        // 确定 preorder 和 postorder 中左右子树的元素区间
        int leftRootVal = preorder[preStart + 1];
        // leftRootVal 在后序遍历数组中的索引
        int index = valToIndex.get(leftRootVal);
        // 左子树的元素个数
        int leftSize = index - postStart + 1;
 
        // 先构造出当前根节点
        TreeNode root = new TreeNode(rootVal);
        // 递归构造左右子树
        // 根据左子树的根节点索引和元素个数推导左右子树的索引边界
        root.left = build(preorder, preStart + 1, preStart + leftSize,
                postorder, postStart, index);
        root.right = build(preorder, preStart + leftSize + 1, preEnd,
                postorder, index + 1, postEnd - 1);
 
        return root;
    }
}

可视化: 东哥带你刷二叉树(构造篇) | labuladong 的算法笔记

代码和前两道题非常类似,我们可以看着代码思考一下,为什么通过前序遍历和后序遍历结果还原的二叉树可能不唯一呢?

关键在这一句:

int leftRootVal = preorder[preStart + 1];

我们假设前序遍历的第二个元素是左子树的根节点,但实际上左子树有可能是空指针,那么这个元素就应该是右子树的根节点。由于这里无法确切进行判断,所以导致了最终答案的不唯一。

至此,通过前序和后序遍历结果还原二叉树的问题也解决了。

最后呼应下前文,二叉树的构造问题一般都是使用「分解问题」的思路:构造整棵树 = 根节点 + 构造左子树 + 构造右子树。先找出根节点,然后根据根节点的值找到左右子树的元素,进而递归构建出左右子树。

现在你是否明白其中的玄妙了呢?