二刷的时候感觉还是有点不太上手,
记录一下思路顺便三刷。
题目描述
思路描述
按照思考顺序
1.前序遍历和中序遍历是如何决定二叉树的
前序遍历->根节点-左子树-右子树
中序遍历->左子树-根节点-右子树
通过前序遍历的每个子树的起始节点可以得到它的根结点,
在得到根结点之后带入中序遍历中可以分割出左子树和右子树。
所以总结出思路
1.先根据前序遍历的第一个结点值得到根结点,带入中序遍历数组;
2.通过中序遍历得到左子树长度之后,根据前序遍历的子树的第一个遍历点是根节点的特性就可以再次带入前序遍历数组中,得到左子树的根结点以及右子树的根结点。
3.根结点和左右子树根结点连接。
4.再次递归,终止条件为根结点无左右子树。
代码实现:
1.建立中序遍历索引
为了方便得到前序遍历的根结点之后带入中序遍历,使用哈希表记录中序遍历的结点索引
unordered_map<int> index;
for(int i = 0; i < inorder.size() ;i ++)
index[inorder[i]] = i ;
2.递归函数实现
TreeNode* recur(int root, int l, int r)
2.1终止条件
由于从中序遍历中可以得到左右子树对应的区间,所以同样通过中序遍历的下标也可以得知该子树是否只有一个根结点。
终止条件->根结点无左右子树结点
if(l>r) return nullptr;//lr为中序遍历数组下标
2.2 递归
首先要建立当前根结点,通过前面分析可以知道要通过前序遍历建立
TreeNode* node = new TreeNode(preorder[root]);
其次我们要在中序遍历中划分左右子树。
于是先得到根结点在中序遍历中的下标
int nR = index[preorder[root]];
2.3 开启递归
2.3.1 左子树,
1.左子树根结点
前序遍历中当前根结点位置的下一个。
2.左子树在中序遍历中的左范围
递归中上一个结点的左范围‘l’
3.左子树在中序遍历中的右范围
中序遍历中当前根结点位置的上一个
node -> left = recur(root + 1, l, nR -1);
2.3.1 右子树
1.右子树根结点
前序遍历中左子树根结点加上左子树长度的下一个。
2.左子树在中序遍历中的左范围
中序遍历中当前根结点位置的下一个
3.左子树在中序遍历中的右范围
递归中上一个结点的右范围‘r’
node -> right = recur(root + nR -l + 1, nR + 1, r);
2.4 回溯
返回当前根结点,用于给父节点连接
return node;
中序遍历相当于把二叉树压扁,在二叉树越左边的数就在中序遍历的越左边。同理右边也是,所以只要确定一个缩小的方向,就能遍历所有节点,且其终止(回溯)的地方必然是最左/最右的地方。
完整代码:
/**
* 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 {
private:
unordered_map <int, int> index;
vector <int> preorder;
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
for(int i = 0; i < inorder.size(); i++)
index[inorder[i]] = i;
this->preorder = preorder;
return recur(0, 0, preorder.size()-1);
}
TreeNode* recur(int root, int l, int r) {
if(l > r)
return nullptr;
TreeNode* node = new TreeNode(preorder[root]);
int nR = index[preorder[root]];
node -> left = recur(root + 1, l, nR -1);
node -> right = recur(root + nR -l + 1, nR + 1, r);
return node;
}
};
时间复杂度O(n),空间复杂度O(n)