题目描述
思路 1
这第一种思路,我是借鉴了 105 的循环解法,对中序后序的序列进行分析,来寻求解答。
如图,
- 当前序和中序同一位置的元素相同时,表示这些点均为左节点,入栈。
- 当遇到第一个不同的节点时,inorder 所在位置表示为某一子树的根节点到达。
- 通过一个 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 的关系。
先上图:
由图我们可以得到两点信息:
- 先序遍历的输出和后续遍历的反向输出,都是以根起始,并且左右成镜像。
- 根据先序后序优先获得的根,来划分中序,从而不断缩小问题的规模
以上两点,就是解答此题的关键。
上代码:
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;
}
}
代码有两点要注意:
- 在递归前,必须先更新 postIndex 的值。因为子问题依赖这个值进行分割
- 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 数组。