题目描述
给定一棵二叉树的前序遍历和中序遍历,请复原出整棵二叉树。
注意:二叉树中没有值相同的节点。
样例
给定:
前序遍历是:[3,9,20,15,7]
中序遍历是:[9,3,15,20,7]
返回的二叉树如下所示:
3
/ \
9 20
/ \
15 7
算法
(递归) $O(n)$
递归建立整棵二叉树:先递归创建左右子树,然后创建根节点,并让指针指向两棵子树。
具体步骤如下:
- 先利用前序遍历找根节点:前序遍历的第一个数,就是根节点的值;
- 在中序遍历中找到根节点的位置 $k$,则 $k$ 左边是左子树的中序遍历,右边是右子树的中序遍历;
- 假设左子树的中序遍历的长度是 $l$,则在前序遍历中,根节点后面的 $l$ 个数,是左子树的前序遍历,剩下的数是右子树的前序遍历;
- 有了左右子树的前序遍历和中序遍历,我们可以先递归创建出左右子树,然后再创建根节点;
时间复杂度分析:我们在初始化时,用哈希表(unordered_map<int,int>
)记录每个值在中序遍历中的位置,这样我们在递归到每个节点时,在中序遍历中查找根节点位置的操作,只需要 $O(1)$ 的时间。此时,创建每个节点需要的时间是 $O(1)$,所以总时间复杂度是 $O(n)$。
C++ 代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
unordered_map<int,int> pos;
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n = preorder.size();
for (int i = 0; i < n; i ++ )
pos[inorder[i]] = i;
return dfs(preorder, inorder, 0, n - 1, 0, n - 1);
}
TreeNode* dfs(vector<int>&pre, vector<int>&in, int pl, int pr, int il, int ir)
{
if (pl > pr) return NULL;
int k = pos[pre[pl]] - il;
TreeNode* root = new TreeNode(pre[pl]);
root->left = dfs(pre, in, pl + 1, pl + k, il, il + k - 1);
root->right = dfs(pre, in, pl + k + 1, pr, il + k + 1, ir);
return root;
}
};
想请教下那个边界问题是怎么考虑到的?就是需要考虑pl>pr的问题
平时多刷点题就好了。
pl > pr 或者 il > ir都行
int k = pos[pre[pl]] - il; y总,这个地方我看讲课的时候没有 -il 呀,为什么 减il和不减都能通过呢
递归的时候需要知道左右子树各自的前序遍历和中序遍历的在愿数组的区间
而区间可以用两种方式表示
1) 【起点下标, 起点+长度 -1】
int k = pos[pre[pl]] - il
这里k用来表示长度2) 【起点下标,终点小标】
int k = pos[pre[pl]]
这里k用来表示下标之前刷剑指offer有这道题,感觉有个很妙的小技巧分享一下,就是利用pop.
棒!
unordered_map 就是这个map的地方理解得不是很透彻,可以具体说一说么
我们有个操作需要在中序遍历中查找某个值在哪个位置,如果直接遍历的话复杂度是 $O(n)$。我们可以先用unordered_map存储每个值的位置,这样在每次查找时只需查询哈希表即可,时间复杂度 $O(1)$。
明白了,谢谢你啊^_^
pl = preorder-left, pr = preorder-right, il = inorder-left, ir = inorder-right
pl, pr分别表示当前子树的前序遍历在整个preorder遍历数组中的起止坐标
il,ir分别表示当前子树的中序遍历在整个inorder数组中的起止下标
这里的left和right不表示左右子树,表示的是数组中区间的左右端点
赞!