题目来源
题目描述
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution {
public:
TreeNode* constructFromPrePost(vector<int>& preorder, vector<int>& postorder) {
}
};
题目解析
思路: 构造二叉树,第一件事一定是找根节点,然后想办法构造左右子树
- 前序遍历的结果是:[1,2,4,5,3,6,7]
- 后序遍历的结果是:[4,5,2,6,7,3,1]
可以看出:
- 前序遍历:根节点始终出现在第一位
- 后序遍历:根节点始终出现在最后一位
但是,虽然能很快确定根节点,但是根节点的左子树的范围没有办法确定,没法确定左子树范围,也会导致右子树也确定不了。所以我们需要确定左子树的范围。如果我们知道左分支有多少个结点,我们就可以对这些数组进行分组,并用递归生成树的每个分支。
我们在仔细观察一下:
- 前序遍历第一个元素是根节点,后面的那一堆是左子树,接着是右子树
- 后序遍历第一个出现的是左子树,然后是右子树,最后才是根节点。
前序遍历和后序中各自的左子树区间的长度肯定是相等的,但是其数字顺序可能是不同的。
我们来看下跟节点1的左子树,如果遍历这个左子树
- 前序遍历的结果是[2,4,5],后序遍历的结果是[4,5,2],我们就可以根据2确定出后序遍历的左子树范围。这个规律对右子树区间同样适用。
- 为什么呢?先序遍历的顺序是 根->左->右,而后序遍历的顺序是 左->右->根,其实这个2就是左子树的根结点,当然会一个在开头,一个在末尾了
class Solution {
public:
TreeNode* constructFromPrePost(vector<int>& pre, vector<int>& post) {
std::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 == nullptr){
st.back()->left = node;
}else{
st.back()->right = node;
}
st.push_back(node);
}
return st[0];
}
};
方法
-
我们知道,在前序遍历的时候,根节点是第一个输出,而在后序遍历中,根节点是最后一个输出;
-
那么我们可以利用前序遍历来构建Tree,然后通过后续遍历来检验当前树是否构建完毕。
-
首先我们创建一个节点作为 root,root = TreeNode(pre[preIndex])当 root.val == post[posIndex]时, 意味着我们已经构建完毕了当前子树,如果当前子树没有构建完毕,那么我们就递归的构建左右子树。
具体代码:
class Solution {
int preIdx = 0, postIdx = 0;
public:
TreeNode* constructFromPrePost(vector<int>& pre, vector<int>& post) {
TreeNode *node = new TreeNode(pre[preIdx++]);
if(node->val != post[postIdx]){
node->left = constructFromPrePost(pre, post);
}
if(node->val != post[postIdx]){
node->right = constructFromPrePost(pre, post);
}
postIdx++;
return node;
}
};
递归写法
用数组模拟栈的后进先出特性:
- 先根据pre数组进行先序创建二叉树。策略是:只要栈顶节点没有左节点,就把当前节点加到栈顶元素的左子节点上,否则加到右子节点上,并把加入的节点压入栈。
- 同时我们用两个指针i和j分别指向在pre和post数组中的位置,如果某个时刻,栈顶元素和post[j]相同了,就说明当前子树已经建立完成了,要将栈中当前的子树全部出栈,知道while循环不满足
- 这样最终建立下来,栈中就只剩下一个根结点了,返回即可
方法
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;
}
};
为了进一步优化时间复杂度,我们可以事先用一个 HashMap,来建立 post 数组中每个元素和其坐标之间的映射,这样在递归函数中,就不用进行查找了,直接在 HashMap 中将其位置取出来用即可,用空间换时间,也不失为一个好的方法,参见代码如下:
class Solution {
unordered_map<int, int> m;
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 = m[pre[preL + 1]], len = (idx - postL) + 1;
node->left = helper(pre, preL + 1, preL + len, post, postL, idx);
node->right = helper(pre, preL + 1 + len, preR, post, idx + 1, postR - 1);
return node;
}
public:
TreeNode* constructFromPrePost(vector<int>& pre, vector<int>& post) {
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);
}
};