[LeetCode] 889. Construct Binary Tree from Preorder and Postorder Traversal 由先序和后序遍历创建二叉树

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

<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/

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)

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

相关文章
相关标签/搜索