105. 从前序与中序遍历序列构造二叉树

❤递归

思路

前序遍历结果:[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]

中序遍历结果:[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]

显然只要定位根节点,便可递归构造二叉树。

该问题的重点便在于:如何快速的在中序遍历中寻找根节点

算法

设递归创建二叉树的函数为build(preorder,inorder,preL,preR,inL,inR)

  1. 取前序遍历第一个点——根节点rootVal.并找到其对应在中序中的位置mid;

  2. 得出左子树长度mid-inL

  3. 递归创建左子树create(preorder,inorder,preL+1,preL+leftLen,inL,mid-1)

  4. 递归创建右子树create(preorder,inorder,preL+leftLen+1,preR,mid+1,inR)K BELONG TOGETHER.
    FOR BEAUTIFUL AND DISTRACTION-FREE WRITING ON YOUR MAC.
    NEW: CLEAN HK GROTESK FONT FAMILY.
    NEW: BETTER CHOSEN COLORS.

  1. class Solution {
  2. public:
  3. TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
  4. int n=preorder.size();
  5. for(int i=0;i<n;i++){
  6. umap[inorder[i]]=i;
  7. }
  8. return create(preorder,inorder,0,n-1,0,n-1);
  9. }
  10. private:
  11. unordered_map<int,int> umap;
  12. TreeNode* create(vector<int>& preorder, vector<int>& inorder,int preL,int preR,int inL,int inR){
  13. if(inL>inR){
  14. return nullptr;
  15. }
  16. int rootVal=preorder[preL];
  17. TreeNode* root=new TreeNode(rootVal);
  18. if(inL==inR){
  19. return root;
  20. }
  21. int mid=umap[rootVal];
  22. int leftLen=mid-inL;
  23. root->left=create(preorder,inorder,preL+1,preL+leftLen,inL,mid-1);
  24. root->right=create(preorder,inorder,preL+leftLen+1,preR,mid+1,inR);
  25. return root;
  26. }
  27. };

复杂度

  • 时间复杂度:O(N)
  • 空间复杂度:O(N)

❤❤迭代

思路

  1. 对于前序遍历任意连续的两个节点uv,其关系仅有如下两种可能:
  • v是u的左儿子

  • 若u无左儿子,则v为u的某个祖先节点(包括u)的右儿子。
    若u无左儿子,则其开始遍历u的右儿子。
    若u无右儿子,则向上回溯,直到遇到第一个有右儿子(u不在其右子树)的节点,其便是v

例子

我们以树

  1. 3
  2. / \
  3. 9 20
  4. / / \
  5. 8 15 7
  6. / \
  7. 5 10
  8. /
  9. 4

为例,它的前序遍历和中序遍历分别为

preorder = [3, 9, 8, 5, 4, 10, 20, 15, 7]
inorder = [4, 5, 8, 10, 9, 3, 15, 20, 7]

我们用一个栈 stack 来维护「当前节点的所有还没有考虑过右儿子的祖先节点」,栈顶就是当前节点。也就是说,只有在栈中的节点才可能连接一个新的右儿子。同时,我们用一个指针 index 指向中序遍历的某个位置,初始值为 0。index 对应的节点是「当前节点不断往左走达到的最终节点」,这也是符合中序遍历的,它的作用在下面的过程中会有所体现。

首先我们将根节点 3 入栈,再初始化 index 所指向的节点为 4,随后对于前序遍历中的每个节点,我们依次判断它是栈顶节点的左儿子,还是栈中某个节点的右儿子。

我们遍历9。9 一定是栈顶节点 3 的左儿子。我们使用反证法,假设 9 是 3 的右儿子,那么 3 没有左儿子,index 应该恰好指向 3,但实际上为 4,因此产生了矛盾。所以我们将 9 作为 3 的左儿子,并将 9 入栈。

  1. stack = [3, 9]
  2. index -> inorder[0] = 4

我们遍历8,5 和 4。同理可得它们都是上一个节点(栈顶节点)的左儿子,所以它们会依次入栈。

  1. stack = [3, 9, 8, 5, 4]
  2. index -> inorder[0] = 4

我们遍历10,这时情况就不一样了。我们发现 index 恰好指向当前的栈顶节点 4,也就是说 4 没有左儿子,那么 10 必须为栈中某个节点的右儿子。那么如何找到这个节点呢?栈中的节点的顺序和它们在前序遍历中出现的顺序是一致的,而且每一个节点的右儿子都还没有被遍历过,那么这些节点的顺序和它们在中序遍历中出现的顺序一定是相反的。
这是因为栈中的任意两个相邻的节点,前者都是后者的某个祖先。并且我们知道,栈中的任意一个节点的右儿子还没有被遍历过,说明后者一定是前者左儿子的子树中的节点,那么后者就先于前者出现在中序遍历中。
因此我们可以把 index 不断向右移动,并与栈顶节点进行比较。如果 index 对应的元素恰好等于栈顶节点,那么说明我们在中序遍历中找到了栈顶节点,所以将 index 增加 1 并弹出栈顶节点,直到 index 对应的元素不等于栈顶节点。按照这样的过程,我们弹出的最后一个节点 x 就是 10 的双亲节点,这是因为 10 出现在了 x 与 x 在栈中的下一个节点的中序遍历之间,因此 10 就是 x 的右儿子。

回到我们的例子,我们会依次从栈顶弹出 4,5 和 8,并且将 index 向右移动了三次。我们将 10 作为最后弹出的节点 8 的右儿子,并将 10 入栈。

  1. 路路 stack = [3, 9, 10]
  2. index -> inorder[3] = 10

我们遍历20。同理,index 恰好指向当前栈顶节点 10,那么我们会依次从栈顶弹出 10,9 和 3,并且将 index 向右移动了三次。我们将 20 作为最后弹出的节点 3 的右儿子,并将 20 入栈。

  1. stack = [20]
  2. index -> inorder[6] = 15

我们遍历 15,将 15 作为栈顶节点 20 的左儿子,并将 15 入栈。

  1. stack = [20, 15]
  2. index -> inorder[6] = 15

我们遍历7。index 恰好指向当前栈顶节点 15,那么我们会依次从栈顶弹出 15 和 20,并且将 index 向右移动了两次。我们将 7 作为最后弹出的节点 20 的右儿子,并将 7 入栈。

  1. stack = [7]
  2. index -> inorder[8] = 7

此时遍历结束,我们就构造出了正确的二叉树。

算法

  • 我们用一个栈和一个指针辅助进行二叉树的构造。初始时栈中存放了根节点(前序遍历的第一个节点),指针指向中序遍历的第一个节点;
  • 我们依次枚举前序遍历中除了第一个节点以外的每个节点。如果 index 恰好指向栈顶节点,那么我们不断地弹出栈顶节点并向右移动 index,并将当前节点作为最后一个弹出的节点的右儿子;如果 index 和栈顶节点不同,我们将当前节点作为栈顶节点的左儿子;
  • 无论是哪一种情况,我们最后都将当前的节点入栈。
  1. class Solution {
  2. public:
  3. TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
  4. if (!preorder.size()) {
  5. return nullptr;
  6. }
  7. TreeNode* root = new TreeNode(preorder[0]);
  8. stack<TreeNode*> stk;
  9. stk.push(root);
  10. int inorderIndex = 0;
  11. for (int i = 1; i < preorder.size(); ++i) {
  12. int preorderVal = preorder[i];
  13. TreeNode* node = stk.top();
  14. if (node->val != inorder[inorderIndex]) {
  15. node->left = new TreeNode(preorderVal);
  16. stk.push(node->left);
  17. }
  18. else {
  19. while (!stk.empty() && stk.top()->val == inorder[inorderIndex]) {
  20. node = stk.top();
  21. stk.pop();
  22. ++inorderIndex;
  23. }
  24. node->right = new TreeNode(preorderVal);
  25. stk.push(node->right);
  26. }
  27. }
  28. return root;
  29. }
  30. };

复杂度

  • 时间复杂度:O(N)
  • 空间复杂度:O(N)

扩展分析

中序和后序生成二叉树?