[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 题目讲解汇总(持续更新中...)

相关文章
相关标签/搜索