LeetCode105-从前序与中序遍历序列构造二叉树
最近全国疫情严重,待在家里没事干,马上又要准备春招了,最近刷刷题,记录一下!再说一句,武汉加油,大家出门记得戴口罩!
1、题目
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
示例:
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
2、思路
如何遍历一棵树
有两种通用的遍历树的策略:
- 宽度优先搜索(BFS):我们按照高度顺序一层一层的访问整棵树,高层次的节点将会比低层次的节点先被访问到。
- 深度优先搜索(DFS):在这个策略中,我们采用深度作为优先级,以便从跟开始一直到达某个确定的叶子,然后再返回根到达另一个分支。
深度优先搜索策略又可以根据根节点、左孩子和右孩子的相对顺序被细分为前序遍历,中序遍历和后序遍历。
我们使用哈希表实现(递归) O(n)
递归建立整棵二叉树:先递归创建左右子树,然后创建根节点,并让指针指向两棵子树。
具体步骤如下:
- 先利用前序遍历找根节点:前序遍历的第一个数,就是根节点的值;
- 在中序遍历中找到根节点的位置 k,则 k 左边是左子树的中序遍历,右边是右子树的中序遍历;
- 假设左子树的中序遍历的长度是 l,则在前序遍历中,根节点后面的 l 个数,是左子树的前序遍历,剩下的数是右子树的前序遍历;
- 有了左右子树的前序遍历和中序遍历,我们可以先递归创建出左右子树,然后再创建根节点;
如图所示:
时间复杂度分析:我们在初始化时,用哈希表(unordered_map<int,int>)记录每个值在中序遍历中的位置,这样我们在递归到每个节点时,在中序遍历中查找根节点位置的操作,只需要 O(1) 的时间。此时,创建每个节点需要的时间是 O(1),所以总时间复杂度是 O(n)。
1. 从左到右遍历preorder、inorder
2. preorder的第一个元素一定是根元素
2.1. 找到根元素后,可以在inorder中区分左、右子树
2.2. 当前根的左子树范围:inorder[inorder未查找的最左边(从0开始), 和根元素相等的索引位置)
2.3. 当前根的左子树范围:preorder(当前根索引位置(从0开始), 当前根的索引位置 + inorder已知左子树长度]
原因:因为preorder、inorder中左、右子树的长度相等(只是观察得出,为什么这么巧,还没想透彻)
3. 当preorder的指针向右移动到"左子树长度",说明当前根节点的左子树已经处理完毕
4. 递归开始查找,如果没有超出左子树范围,preorder指针向右移动一位继续搜索
4.1. 此时的节点最多只可能有2种身份:A + B
A. 根节点
B. 左节点或右节点
4.2. inorder的起始节点是"最左的节点",当preorder中的值与他相等时,可以判断无后续数据,结束搜索
4.3 inorder指针向右移动一位(排除已使用节点),缩小搜索范围
5. 右节点确定规则:因为上一步确定的是一个左节点,preorder顺序为根左右,所以preorder的下一个节点就是右节点
3、代码
递归实现:
class Solution {
public:
//定义哈希表存储中序遍历中每个点的位置
unordered_map<int,int> Hash;
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
//计算整颗子树的大小
int n=preorder.size();
//哈希表存储中序遍历中每个点的位置
for(int i=0;i<n;i++)
{
Hash[inorder[i]]=i;
}
return dfs(preorder,inorder,0,n-1,0,n-1);
}
TreeNode* dfs(vector<int>& preorder,vector<int>& inorder,int pl,int pr,int il,int ir)
{
if (pl > pr) return NULL;
//根节点的值
int val=preorder[pl];
int k=Hash[val];//距离
int len=k-il;
//定义根节点
auto root=new TreeNode(val);
root->left=dfs(preorder,inorder,pl+1,pl+len,il,k-1);
root->right=dfs(preorder,inorder,pl+len+1,pr,k+1,ir);
return root;
}
};
Java
class Solution {
//定义哈希表,存储中序遍历每个点的位置
HashMap<Integer,Integer> map = new HashMap<Integer, Integer>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
return dfs(preorder, 0, preorder.length, inorder, 0, inorder.length);
}
private TreeNode dfs(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
// preorder 为空,直接返回 null
if (p_start == p_end) {
return null;
}
int root_val = preorder[p_start];
TreeNode root = new TreeNode(root_val);
//在中序遍历中找到根节点的位置
//int i_root_index = 0;
/*
for (int i = i_start; i < i_end; i++) {
if (root_val == inorder[i]) {
i_root_index = i;
break;
}
}
*/
int i_root_index = map.get(root_val);
//k i_root_index
//leftNum len
int leftNum = i_root_index - i_start;
//递归的构造左子树
root.left =dfs(preorder, p_start + 1, p_start + leftNum + 1, inorder, i_start,i_root_index);
//递归的构造右子树
root.right =dfs(preorder, p_start + leftNum + 1, p_end, inorder, i_root_index + 1, i_end);
return root;
}
}