Return any binary tree that matches the given preorder and postorder traversals.

Values in the traversals pre and post are distinct positive integers.

Example 1:

Input: pre = [1,2,4,5,3,6,7], post = [4,5,2,6,7,3,1]
Output: [1,2,3,4,5,6,7]

Note:

  • 1 <= pre.length == post.length <= 30
  • pre[] and post[] are both permutations of 1, 2, ..., pre.length.
  • It is guaranteed an answer exists. If there exists multiple answers, you can return any of them.

这道题给了一棵树的先序遍历和后序遍历的数组,让我们根据这两个数组来重建出原来的二叉树。之前也做过二叉树的先序遍历 [Binary Tree Preorder Traversal](http://www.cnblogs.com/grandyang/p/4146981.html) 和 后序遍历 [Binary Tree Postorder Traversal](http://www.cnblogs.com/grandyang/p/4251757.html),所以应该对其遍历的顺序并不陌生。其实二叉树最常用的三种遍历方式,先序,中序,和后序遍历,只要知道其中的任意两种遍历得到的数组,就可以重建出原始的二叉树,而且正好都在 LeetCode 中有出现,其他两道分别是 [Construct Binary Tree from Inorder and Postorder Traversal](https://www.cnblogs.com/grandyang/p/4296193.html) 和 [Construct Binary Tree from Preorder and Inorder Traversal](https://www.cnblogs.com/grandyang/p/4296500.html)。如果做过之前两道题,那么这道题就没有什么难度了,若没有的话,可能还是有些 tricky 的,虽然这仅仅只是一道 Medium 的题。

我们知道,先序遍历的顺序是 根->左->右,而后序遍历的顺序是 左->右->根,既然要建立树,那么肯定要从根结点开始创建,然后再创建左右子结点,若你做过很多树相关的题目的话,就会知道大多数都是用递归才做,那么创建的时候也是对左右子结点调用递归来创建。心中有这么个概念就好,可以继续来找这个重复的 pattern。由于先序和后序各自的特点,根结点的位置是固定的,既是先序遍历数组的第一个,又是后序遍历数组的最后一个,而如果给我们的是中序遍历的数组,那么根结点的位置就只能从另一个先序或者后序的数组中来找了,但中序也有中序的好处,其根结点正好分割了左右子树,就不在这里细讲了,还是回到本题吧。知道了根结点的位置后,我们需要分隔左右子树的区间,先序和后序的各个区间表示如下:

preorder -> [root] [left subtree] [right subtree]

postorder -> [left subtree] [right substree] [root]

具体到题目中的例子就是:

preorder -> [1] [2,4,5] [3,6,7]

postorder -> [4,5,2] [6,7,3] [root]

先序和后序中各自的左子树区间的长度肯定是相等的,但是其数字顺序可能是不同的,但是我们仔细观察的话,可以发现先序左子树区间的第一个数字2,在后序左右子树区间的最后一个位置,而且这个规律对右子树区间同样适用,这是为啥呢,这就要回到各自遍历的顺序了,先序遍历的顺序是 根->左->右,而后序遍历的顺序是 左->右->根,其实这个2就是左子树的根结点,当然会一个在开头,一个在末尾了。发现了这个规律,就可以根据其来定位左右子树区间的位置范围了。既然要拆分数组,那么就有两种方式,一种是真的拆分成小的子数组,另一种是用双指针来指向子区间的开头和末尾。前一种方法无疑会有大量的数组拷贝,不是很高效,所以我们这里采用第二种方法来做。用 preL 和 preR 分别表示左子树区间的开头和结尾位置,postL 和 postR 表示右子树区间的开头和结尾位置,那么若 preL 大于 preR 或者 postL 大于 postR 的时候,说明已经不存在子树区间,直接返回空指针。然后要先新建当前树的根结点,就通过 pre[preL] 取到即可,接下来要找左子树的根结点在 post 中的位置,最简单的方法就是遍历 post 中的区间 [postL, postR],找到其位置 idx,然后根据这个 idx,就可以算出左子树区间长度为 len = (idx-postL)+1,那么 pre 数组中左子树区间为 [preL+1, preL+len],右子树区间为 [preL+1+len, preR],同理,post 数组中左子树区间为 [postL, idx],右子树区间为 [idx+1, postR-1]。知道了这些信息,就可以分别调用递归函数了,参见代码如下:

解法一:

class Solution {
public:
TreeNode* constructFromPrePost(vector<int>& pre, vector<int>& post) {
return helper(pre, 0, (int)pre.size() - 1, post, 0, (int)post.size() - 1);
}
TreeNode* helper(vector<int>& pre, int preL, int preR, vector<int>& post, int postL, int postR) {
if (preL > preR || postL > postR) return nullptr;
TreeNode *node = new TreeNode(pre[preL]);
if (preL == preR) return node;
int idx = -1;
for (idx = postL; idx <= postR; ++idx) {
if (pre[preL + 1] == post[idx]) break;
}
node->left = helper(pre, preL + 1, preL + 1 + (idx - postL), post, postL, idx);
node->right = helper(pre, preL + 1 + (idx - postL) + 1, preR, post, idx + 1, postR - 1);
return node;
}
};

我们也可以使用 STL 内置的 find() 函数来查找左子树的根结点在 post 中的位置,其余的地方都跟上面的解法相同,参见代码如下:


解法二:

class Solution {
public:
TreeNode* constructFromPrePost(vector<int>& pre, vector<int>& post) {
return helper(pre, 0, (int)pre.size() - 1, post, 0, (int)post.size() - 1);
}
TreeNode* helper(vector<int>& pre, int preL, int preR, vector<int>& post, int postL, int postR) {
if (preL > preR || postL > postR) return nullptr;
TreeNode *node = new TreeNode(pre[preL]);
if (preL == preR) return node;
int idx = find(post.begin() + postL, post.begin() + postR + 1, pre[preL + 1]) - post.begin();
node->left = helper(pre, preL + 1, preL + 1 + (idx - postL), post, postL, idx);
node->right = helper(pre, preL + 1 + (idx - postL) + 1, preR, post, idx + 1, postR - 1);
return node;
}
};

为了进一步优化时间复杂度,我们可以事先用一个 HashMap,来建立 post 数组中每个元素和其坐标之间的映射,这样在递归函数中,就不用进行查找了,直接在 HashMap 中将其位置取出来用即可,用空间换时间,也不失为一个好的方法,参见代码如下:


解法三:

class Solution {
public:
TreeNode* constructFromPrePost(vector<int>& pre, vector<int>& post) {
unordered_map<int, int> m;
for (int i = 0; i < post.size(); ++i) m[post[i]] = i;
return helper(pre, 0, (int)pre.size() - 1, post, 0, (int)post.size() - 1, m);
}
TreeNode* helper(vector<int>& pre, int preL, int preR, vector<int>& post, int postL, int postR, unordered_map<int, int>& m) {
if (preL > preR || postL > postR) return nullptr;
TreeNode *node = new TreeNode(pre[preL]);
if (preL == preR) return node;
int idx = m[pre[preL + 1]], len = (idx - postL) + 1;
node->left = helper(pre, preL + 1, preL + len, post, postL, idx, m);
node->right = helper(pre, preL + 1 + len, preR, post, idx + 1, postR - 1, m);
return node;
}
};

论坛上 [lee215 大神](https://leetcode.com/problems/construct-binary-tree-from-preorder-and-postorder-traversal/discuss/161268/C%2B%2BJavaPython-One-Pass-Real-O(N)) 提出了一种迭代的写法,借助了栈来做,其实就用个数组就行,模拟栈的后进先出的特性。这种设计思路很巧妙,现根据 pre 数组进行先序创建二叉树,当前我们的策略是,只要栈顶结点没有左子结点,就把当前结点加到栈顶元素的左子结点上,否则加到右子结点上,并把加入的结点压入栈。同时我们用两个指针i和j分别指向当前在 pre 和 post 数组中的位置,若某个时刻,栈顶元素和 post[j] 相同了,说明当前子树已经建立完成了,要将栈中当前的子树全部出栈,直到 while 循环的条件不满足。这样最终建立下来,栈中就只剩下一个根结点了,返回即可,参见代码如下:


解法四:

class Solution {
public:
TreeNode* constructFromPrePost(vector<int>& pre, vector<int>& post) {
vector<TreeNode*> st;
st.push_back(new TreeNode(pre[0]));
for (int i = 1, j = 0; i < pre.size(); ++i) {
TreeNode *node = new TreeNode(pre[i]);
while (st.back()->val == post[j]) {
st.pop_back();
++j;
}
if (!st.back()->left) st.back()->left = node;
else st.back()->right = node;
st.push_back(node);
}
return st[0];
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/889

类似题目:

Binary Tree Preorder Traversal

Binary Tree Postorder Traversal

Construct Binary Tree from Inorder and Postorder Traversal

Construct Binary Tree from Preorder and Inorder Traversal

参考资料:

https://leetcode.com/problems/construct-binary-tree-from-preorder-and-postorder-traversal/

https://leetcode.com/problems/construct-binary-tree-from-preorder-and-postorder-traversal/discuss/161286/C%2B%2B-O(N)-recursive-solution

https://leetcode.com/problems/construct-binary-tree-from-preorder-and-postorder-traversal/discuss/163540/Java-recursive-solution-beat-99.9

https://leetcode.com/problems/construct-binary-tree-from-preorder-and-postorder-traversal/discuss/161268/C%2B%2BJavaPython-One-Pass-Real-O(N)

[LeetCode All in One 题目讲解汇总(持续更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)

最新文章

  1. Linux tar (打包.压缩.解压缩)命令说明 | tar如何解压文件到指定的目录?
  2. Python 开平方
  3. SSH(Struts Spring Hibernate开发框架)
  4. 演示save point及自治事务的用处
  5. Unity3D Shader入门指南(一)
  6. filesort是什么意思?
  7. Arch 安装步骤
  8. jQuery--Promise object
  9. Unity 3.5
  10. 对象之function
  11. Ibatis collect select用法详解
  12. C++函数调用的反汇编过程及Thunk应用
  13. poj2051 Argus
  14. [Spark内核] 第33课:Spark Executor内幕彻底解密:Executor工作原理图、ExecutorBackend注册源码解密、Executor实例化内幕、Executor具体工作内幕
  15. codeforces#1154F. Shovels Shop (dp)
  16. 「BZOJ2733」「洛谷3224」「HNOI2012」永无乡【线段树合并】
  17. service docker start后docker stop/waiting的解决方法
  18. 【CF526F】Pudding Monsters
  19. 分析Linux上的程序依赖
  20. 原来通过修改dns加快app store下载速度的确有效

热门文章

  1. 如何在yii1.0.7中设置数据库连接超时?
  2. JVM的监控工具之jinfo
  3. Logstash:处理多个input
  4. 阿里云(百度云)Linux系统挂载磁盘
  5. ASP.NET Core快速入门(第6章:ASP.NET Core MVC)--学习笔记
  6. jsonp格式前端发送和后台接受写法
  7. Flutter 安装笔记
  8. SPC软控件提供商NWA的产品在各行业的应用(生命科学行业)
  9. Linux signal与定时器
  10. Flink使用SideOutPut替换Split实现分流