C++实现LeetCode(889.由先序和后序遍历建立二叉树)

 更新时间:2021年07月22日 14:29:14   作者:Grandyang  
这篇文章主要介绍了C++实现LeetCode(889.由先序和后序遍历建立二叉树),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

[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 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)

到此这篇关于C++实现LeetCode(889.由先序和后序遍历建立二叉树)的文章就介绍到这了,更多相关C++实现由先序和后序遍历建立二叉树内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言控制语句之 循环

    C语言控制语句之 循环

    C语言循环控制语句是一个基于C语言的编程语句,该语句主要有while循环语句、do-while循环语句和for循环语句来实现循环结构,今天通过本文给大家分享C控制语句的相关知识,需要的朋友参考下吧
    2021-07-07
  • VScode配置C++运行环境的完整步骤

    VScode配置C++运行环境的完整步骤

    这篇文章主要给大家介绍了关于VScode配置C++运行环境的完整步骤,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • OpenCV实现直线检测并消除

    OpenCV实现直线检测并消除

    这篇文章主要为大家详细介绍了OpenCV实现直线检测并消除,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • C语言实现俄罗斯方块源代码

    C语言实现俄罗斯方块源代码

    这篇文章主要为大家详细介绍了C语言实现俄罗斯方块的源代码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-06-06
  • Qt学习之容器的使用详解

    Qt学习之容器的使用详解

    Qt容器主要优点就是在所有的平台上的运行都表现的一致,并且它们都是隐含共享的,这篇文章就来和大家讲讲Qt中容器的具体用法吧,希望对大家有所帮助
    2023-03-03
  • c语言颜色代码详解

    c语言颜色代码详解

    在本篇文章里小编给大家整理的是关于c语言颜色代码的知识点内容,需要的朋友们可以参考下。
    2020-02-02
  • 深入理解goto语句的替代实现方式分析

    深入理解goto语句的替代实现方式分析

    本篇文章是对goto语句的替代实现方式进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • HDOJ 1443 约瑟夫环的最新应用分析详解

    HDOJ 1443 约瑟夫环的最新应用分析详解

    本篇文章是对HDOJ 1443 约瑟夫环的最新应用进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • Linux C/C++实现网络流量分析工具

    Linux C/C++实现网络流量分析工具

    网络流量分析的原理基于对数据包的捕获、解析和统计分析,通过对网络流量的细致观察和分析,帮助管理员了解和优化网络的性能,本文将通过C++实现网络流量分析工具,有需要的可以参考下
    2023-10-10
  • C++实现当前时间动态显示的方法

    C++实现当前时间动态显示的方法

    这篇文章主要介绍了C++实现当前时间动态显示的方法,涉及C++时间操作及Sleep方法的使用,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07

最新评论