<br> Return any binary tree that matches the given preorder and postorder traversals.html
Values in the traversals pre
and post
are distinct positive integers.node
Example 1:git
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:github
1 <= pre.length == post.length <= 30
pre[]
and post[]
are both permutations of 1, 2, ..., pre.length
.<br> 這道題給了一棵樹的先序遍歷和後序遍歷的數組,讓咱們根據這兩個數組來重建出原來的二叉樹。以前也作過二叉樹的先序遍歷 [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]post
具體到題目中的例子就是:優化
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]。知道了這些信息,就能夠分別調用遞歸函數了,參見代碼以下:指針
<br> 解法一:
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; } };
<br> 咱們也可使用 STL 內置的 find() 函數來查找左子樹的根結點在 post 中的位置,其他的地方都跟上面的解法相同,參見代碼以下:
<br> 解法二:
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; } };
<br> 爲了進一步優化時間複雜度,咱們能夠事先用一個 HashMap,來創建 post 數組中每一個元素和其座標之間的映射,這樣在遞歸函數中,就不用進行查找了,直接在 HashMap 中將其位置取出來用便可,用空間換時間,也不失爲一個好的方法,參見代碼以下:
<br> 解法三:
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; } };
<br> 論壇上 [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 循環的條件不知足。這樣最終創建下來,棧中就只剩下一個根結點了,返回便可,參見代碼以下:
<br> 解法四:
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]; } };
<br> Github 同步地址:
https://github.com/grandyang/leetcode/issues/889
<br> 相似題目:
Binary Tree Preorder Traversal
Binary Tree Postorder Traversal
Construct Binary Tree from Inorder and Postorder Traversal
Construct Binary Tree from Preorder and Inorder Traversal
<br> 參考資料:
https://leetcode.com/problems/construct-binary-tree-from-preorder-and-postorder-traversal/
<br> [LeetCode All in One 題目講解彙總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)