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)

[LeetCode] 889. Construct Binary Tree from Preorder and Postorder Traversal 由先序和后序遍历建立二叉树的更多相关文章

  1. LeetCode 889. Construct Binary Tree from Preorder and Postorder Traversal

    原题链接在这里:https://leetcode.com/problems/construct-binary-tree-from-preorder-and-postorder-traversal/ 题 ...

  2. (二叉树 递归) leetcode 889. Construct Binary Tree from Preorder and Postorder Traversal

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

  3. LC 889. Construct Binary Tree from Preorder and Postorder Traversal

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

  4. 【LeetCode】889. Construct Binary Tree from Preorder and Postorder Traversal 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  5. [LeetCode] 105. Construct Binary Tree from Preorder and Inorder Traversal 由先序和中序遍历建立二叉树

    Given preorder and inorder traversal of a tree, construct the binary tree. Note:You may assume that ...

  6. (二叉树 递归) leetcode 105. Construct Binary Tree from Preorder and Inorder Traversal

    Given preorder and inorder traversal of a tree, construct the binary tree. Note:You may assume that ...

  7. (二叉树 递归) leetcode 106. Construct Binary Tree from Inorder and Postorder Traversal

    Given inorder and postorder traversal of a tree, construct the binary tree. Note:You may assume that ...

  8. [LeetCode] 106. Construct Binary Tree from Inorder and Postorder Traversal 由中序和后序遍历建立二叉树

    Given inorder and postorder traversal of a tree, construct the binary tree. Note:You may assume that ...

  9. [Leetcode Week14]Construct Binary Tree from Inorder and Postorder Traversal

    Construct Binary Tree from Inorder and Postorder Traversal 题解 原创文章,拒绝转载 题目来源:https://leetcode.com/pr ...

随机推荐

  1. asp.net core系列 63 领域模型架构 eShopOnWeb项目分析 上

    一.概述 本篇继续探讨web应用架构,讲基于DDD风格下最初的领域模型架构,不同于DDD风格下CQRS架构,二者架构主要区别是领域层的变化. 架构的演变是从领域模型到CQRS,  一开始DDD是用领域 ...

  2. Autoware 培训笔记 No. 1——构建点云地图

    1. 首记 相信许多刚开始玩无人驾驶的人都用过Autoware,对runtime manager都比较熟悉,虽然可以通过各种渠道了解到有些设置,甚至有些设置的app下参数的含义,但是,在真车的使用过程 ...

  3. F#周报2019年第24期

    新闻 ML.NET 1.1发布与模型构建器升级 .NET Core 3.0预览版6发布 尝试新的System.Text.Json API F#调用Infer.NET 匿名记录类型文档 了解FableC ...

  4. WPF-如何添加用户控件(同一个程序集与非同一个程序集)

    在WPF中,假如十个按钮与十个文本框需要在窗体中多次使用,每次都都要重新添加这二十个按钮,显然是不可取的.这时,可以把这二十个按钮封装成一个UserControl,然后多次引用. 一.新建一个用户控件 ...

  5. C# .NET 使用 NPOI 读取 .xlsx 格式 Excel

    string filePath = @"C:\Users\yangqinglin\Desktop\test.xlsx"; IWorkbook wk = null; string e ...

  6. PhantomJS简单使用

    PhantomJS下载地址:   http://phantomjs.org/download.html 简单使用: from selenium import webdriver # 要想调用键盘按键操 ...

  7. IOS开发实战-Xcode创建HelloWorld项目

    一.创建工程打开Xcode开发工具,在Welcome界面选择”Create a new Xcode project”选项 在选择模板窗口,选择”Single View Application” 确定模 ...

  8. SharpGL之透视投影和摄像机

    当三维体放在世界坐标系中后,由于显示器只能用二维图像显示三维休,因此必须要依赖投影来把三维体降低维数. 投影变换的目的就是定义了一个视景体,使得视景体外多余的部分不会显示. 投影包括透视投影(pers ...

  9. SQLi-LABS Page-2 (Adv Injections) Less30-Less35

    Less-30 GET - BLIND - IMPIDENCE MISMATCH- Having http://10.10.202.112/sqli/Less-30?id=1" #false ...

  10. Jmeter进阶技能-数据库信息,传递参数

    因为项目的原因,假设我们要实现如下要求:从数据库的用户表里获取用户信息,并作为参数全部传递给登录请求,分别完成登录操作. 01Jmeter连接数据库 1.添加JDBC Connection Confi ...