【剑指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题解。
通过前序遍历和中序遍历重建二叉树的主要流程为:
- 前序遍历负责提供root节点;
- 通过字典找到上述root节点在中序遍历中的位置;
- 通过该位置划分左子树和右子树;
- 遍历左子树;
- 遍历右子树;
- 返回节点,最后回退到根结点时,代表树构建完成。
解答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,我们可以使用一个栈来保存候选祖先节点。候选祖先节点满足“已经入树、不是叶节点、尚未生成右子树”这三个要求。在没有右子树的情况下,中序遍历是前序遍历的翻转。那么就意味着,当两个遍历不满足这个关系的时候,一定在不相同的那一处存在右子树。当栈顶元素值s.top()->val
与中序遍历指针值inorder[j]
相同时,表明该栈顶元素不包含右子树。此时推出栈顶元素到工作指针cur
中,中序遍历指针后移。直到栈空或者不相同时,此时工作指针值即为要找的祖先节点,将前序遍历的下一个候选节点preorder[i]
作为该祖先节点的右节点即可。