【剑指Offer-07】重建二叉树

问题

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

// Definition for a binary tree node.
  struct TreeNode {
       int val;
       TreeNode *left;
       TreeNode *right;
       TreeNode(int x) : val(x), left(NULL), right(NULL) {}
   };

示例

给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]

返回如下的二叉树:
    3
   / \
  9   20
       / \
      15   7

解答1:递归

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = inorder.size();
        for (int i = 0; i < n; i++)
            ump[inorder[i]] = i; // 为中序遍历每个值存储角标,方便查找root位置
        return recur(preorder, 0, 0, n - 1);
    }
private:
    unordered_map<int, int> ump;
    // root为前序遍历上的角标,left、right为中序遍历上的角标
    TreeNode* recur(vector<int>& preorder, int root, int left, int right) {
        if (left > right) return nullptr; //递归终止条件
        TreeNode* cur = new TreeNode(preorder[root]); // 新建root节点
        int i = ump[preorder[root]]; // root节点在中序遍历中的位置
        cur->left = recur(preorder, root + 1, left, i - 1); // 左子树递归
        cur->right = recur(preorder, root + i - left + 1, i + 1, right); // 右子树递归,i - left + 1为左子树的长度
        return cur;
    }
};

重点思路

具体可看该LeetCode题解
通过前序遍历和中序遍历重建二叉树的主要流程为:

  1. 前序遍历负责提供root节点;
  2. 通过字典找到上述root节点在中序遍历中的位置;
  3. 通过该位置划分左子树和右子树;
  4. 遍历左子树;
  5. 遍历右子树;
  6. 返回节点,最后回退到根结点时,代表树构建完成。

解答2:迭代

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if (!preorder.size()) return nullptr;
        stack<TreeNode*> s;
        TreeNode* root = new TreeNode(preorder[0]);
        TreeNode* cur = root; // cur为工作指针,用于树的生成
        // j为中序遍历上的指针,i为前序遍历上的指针
        for (int i = 1, j = 0; i < preorder.size(); i++) { 
            if (cur->val != inorder[j]) { // 问题1阶段
                s.push(cur);
                cur = cur->left = new TreeNode(preorder[i]);
            } else { // 问题2阶段
                j++;
                while (!s.empty() && s.top()->val == inorder[j]) {
                    cur = s.top();
                    s.pop();
                    j++;
                }
                cur = cur->right = new TreeNode(preorder[i]);
            }
        }
        return root;
    }
};

重点思路

观察前序遍历可知,相邻两个元素,后一个要么是前一个都左节点,要么是前一个元素某个祖先节点(包括自己)的右节点。所以要解决该问题,重点在于:

  1. 判断属于这两种情况的哪一种;
  2. 定位该祖先节点。

对于问题1,可以使用中序遍历的特性解决。中序遍历的顺序为“左节点->根节点->右节点”,那么我们可以使用一个指针由首到尾遍历该中序遍历,指向的值为还没入树的值中的最左节点。当前序遍历值与指向的值相等时,则意味着这一阶段的左子树生成到头了,该回头寻找祖先节点生成右子树了。

对于问题2,我们可以使用一个栈来保存候选祖先节点。候选祖先节点满足“已经入树、不是叶节点、尚未生成右子树”这三个要求。在没有右子树的情况下,中序遍历是前序遍历的翻转。那么就意味着,当两个遍历不满足这个关系的时候,一定在不相同的那一处存在右子树。当栈顶元素值s.top()->val与中序遍历指针值inorder[j]相同时,表明该栈顶元素不包含右子树。此时推出栈顶元素到工作指针cur中,中序遍历指针后移。直到栈空或者不相同时,此时工作指针值即为要找的祖先节点,将前序遍历的下一个候选节点preorder[i]作为该祖先节点的右节点即可。

posted @ 2021-02-15 09:53  tmpUser  阅读(24)  评论(0编辑  收藏  举报