Leetcode 106:从中序后序构建树

题目描述

在这里插入图片描述

思路 1

这第一种思路,我是借鉴了 105 的循环解法,对中序后序的序列进行分析,来寻求解答。
在这里插入图片描述
如图,

  1. 当前序和中序同一位置的元素相同时,表示这些点均为左节点,入栈。
  2. 当遇到第一个不同的节点时,inorder 所在位置表示为某一子树的根节点到达。
  3. 通过一个 father 变量标识右节点的链接。

代码如下:

public class Solution1 {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        int i = 0, j = 0;
        Stack<Integer> stack = new Stack<>();
        TreeNode res = null;
        TreeNode father = null;

        while (i != inorder.length) {
            if (inorder[i] == postorder[j]) {
                stack.push(inorder[i]);
                i ++;
                j ++;
            }
            else {
                TreeNode currRoot = new TreeNode(inorder[i]);
                currRoot.left = buildTreeFromStack(stack);
                if (father != null) {
                    father.right = currRoot;
                }
                else {
                    res = currRoot;
                }
                i ++;
                father = currRoot;
            }
        }

        if (!stack.isEmpty()) {
            if (father == null) res = buildTreeFromStack(stack);
            else {
                father.right = buildTreeFromStack(stack);
            }
        }

        return res;

    }

    public TreeNode buildTreeFromStack(Stack<Integer> stack) {
        if (stack.isEmpty()) return null;

        TreeNode res = new TreeNode(stack.pop());
        TreeNode prev = res;
        while (!stack.isEmpty()) {
            prev.left =  new TreeNode(stack.pop());
            prev = prev.left;
        }

        return res;
    }
}

为了测试这种代码,我使用了几个特殊的测例:

第一个:
在这里插入图片描述

第二个:

在这里插入图片描述
本来上面的测例都还可以,但到了下面这个测例,直接推翻了这个算法:

在这里插入图片描述

使用我的算法,跑出来的是下面这种树,然而正确的是上面的树。
究其原因,我把 inorder与 postorder 相同的节点当成了左节点。然而情况是,不同位置的相同节点,就不是左节点。

[左,中,右]
[左,右,中]

假如对应位置与下一位置恰好相反,则符合上述的情况。

所以上述算法从思路上就错了。

思路 2

查了一下答案,才发现这道题很简单。没有解决的原因是,没有真正理清该题与 105 的关系。

先上图:
在这里插入图片描述
由图我们可以得到两点信息:

  1. 先序遍历的输出和后续遍历的反向输出,都是以根起始,并且左右成镜像。
  2. 根据先序后序优先获得的根,来划分中序,从而不断缩小问题的规模

以上两点,就是解答此题的关键。

上代码:


public class Solution {

    private Map<Integer, Integer> inorderMap = new HashMap<>();
    private int[] inorder;
    private int[] postorder;
    private int postIndex;

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        this.inorder = inorder;
        this.postorder = postorder;
        for(int i = 0; i < inorder.length; ++i) {
            inorderMap.put(inorder[i], i);
        }
        postIndex = postorder.length - 1;
        return build(0, inorder.length - 1);
    }

    public TreeNode build(int inLeft, int inRight) {

        if (inLeft > inRight) return null;

        int rootval = postorder[postIndex];
        TreeNode rootNode = new TreeNode(rootval);
        int inIndex= inorderMap.get(rootval);

        postIndex--;

        rootNode.right = build(inIndex + 1, inRight); // 必须先右
        rootNode.left = build(inLeft, inIndex - 1);

        return rootNode;

    }
}

代码有两点要注意:

  1. 在递归前,必须先更新 postIndex 的值。因为子问题依赖这个值进行分割
  2. rootNode 必须先右后左。原因就是上面提到的,后序反向的输出是:中右左。

根据这个代码,我们可以对 105 的代码做一些优化:


public class Solution3 {
    private int[] preorder;
    private int[] inorder;
    private int preIndex;
    private Map<Integer, Integer> inorderMap = new HashMap<>();


    public TreeNode buildTree(int[] preorder, int[] inorder) {
        init(preorder, inorder);
        return helper(0, inorder.length - 1);
    }

    public void init(int[] preorder, int[] inorder) {
        this.preorder = preorder;
        this.inorder = inorder;
        this.preIndex = 0;
        for (int i = 0; i < inorder.length; ++i) {
            inorderMap.put(inorder[i], i);
        }
    }

    public TreeNode helper(int inLeft, int inRight) {
        if (inLeft > inRight) return null;

        int rootval = preorder[preIndex];
        int inIndex = inorderMap.get(rootval);

        TreeNode root = new TreeNode(rootval);
        preIndex ++; // 在递归之前就要更新!!!

        root.left = helper(inLeft, inIndex - 1); // 先左边
        root.right = helper(inIndex + 1, inRight);


        return root;
    }
}

可以看到递归部分代码与之前相比省略了重复传递的 inorder 与 preorder 数组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值