[LeetCode] 889. Construct Binary Tree from Preorder and Postorder Traversal 由先序和後序遍歷創建二叉樹



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.
  • It is guaranteed an answer exists. If there exists multiple answers, you can return any of them.



這道題給了一棵樹的先序遍歷和後序遍歷的數組,讓咱們根據這兩個數組來重建出原來的二叉樹。以前也作過二叉樹的先序遍歷 Binary Tree Preorder Traversal 和 後序遍歷 Binary Tree Postorder Traversal,因此應該對其遍歷的順序並不陌生。其實二叉樹最經常使用的三種遍歷方式,先序,中序,和後序遍歷,只要知道其中的任意兩種遍歷獲得的數組,就能夠重建出原始的二叉樹,並且正好都在 LeetCode 中有出現,其餘兩道分別是 Construct Binary Tree from Inorder and Postorder TraversalConstruct Binary Tree from Preorder and Inorder Traversal。若是作過以前兩道題,那麼這道題就沒有什麼難度了,若沒有的話,可能仍是有些 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]。知道了這些信息,就能夠分別調用遞歸函數了,參見代碼以下:指針



解法一:

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 大神 提出了一種迭代的寫法,藉助了棧來作,其實就用個數組就行,模擬棧的後進先出的特性。這種設計思路很巧妙,現根據 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 題目講解彙總(持續更新中...)

相關文章
相關標籤/搜索