【努力刷力扣】第二十天 --- 中序先序重整二叉树
引言:全部算法建立在无重复点的基础上
本人初次尝试写博客,希望各位看官大佬多多包容
有错误希望巨巨们提出来,我一定会及时改正,谢谢大家
在自己最好的年纪,找到了未来的目标
还有1年奋斗刷题,明年去面试实习,加油!
老样子,先看看题目要求:
简单分划重组二叉树
前序知识:
1、先根遍历每个节点都可以理解为一个根,所以对先序第一个节点理解成根
2、在中序遍历中只要找到根节点的位置就可以将之分为左子树+右子树
前序遍历性质: 节点按照 [ 根节点 | 左子树 | 右子树 ] 排序。
中序遍历性质: 节点按照 [ 左子树 | 根节点 | 右子树 ] 排序。
策略 :建立节点回溯重组
有了前序知识,这题就很简单了,找到出口,把该节点建立,然后把前序和中序进行分解,找到左右子树拼上返回自己即可,所以这明显的回溯模型。出口+左右处理+本节点处理+返回。
具体分析
该算法本质就可以转变成每一次对先序中序的分划,先由先序找到根节点,再在中序分出左右子树,看左子树多长,最后将先序拆分
代码
注:在找范围的时候,可以先根据简单的示例写出来,在不断验证即可。
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)
{
int num_pre = preorder.size();
int num_in = inorder.size();
if (num_pre == 0) {//如果分划出的先序或者中序(二者长度一样)为零了代表结束
return nullptr;
}
TreeNode * root = new TreeNode(preorder.front());//先把根拿出来
int i = 0;
for (; i < num_in; i++) {//找到中序中根的位置
if (inorder[i] == preorder.front()) {
break;
}
}
vector<int> left_inorder, right_inorder, left_preorder, right_preorder;//之后是对先序后续的分割,以下范围可以看上图去理解
for (int j = 0; j < i; j++) {
left_inorder.push_back(inorder[j]);
}
for (int j = i + 1; j < num_in; j++) {
right_inorder.push_back(inorder[j]);
}
for (int j = 1; j < i + 1; j++) {//每一次小的preorder第一个为根,所以新的从后一个开始
left_preorder.push_back(preorder[j]);
}
for (int j = i + 1; j < num_pre; j++) {
right_preorder.push_back(preorder[j]);
}
TreeNode * left = buildTree(left_preorder, left_inorder);//后根
TreeNode * right = buildTree(right_preorder, right_inorder);
root->left = left;//连接
root->right = right;
return root;//返回
}
};
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短时间):
(时间复杂度在下方)
时间复杂度: O(N)
总结1
该方法算法层面是正确的,但是可以看到时间耗费上是非常巨大的,无法接受,所以这个算法思想不变,但是实现方法要去优化,下面将介绍优化算法。
优化后的回溯算法
通过观察很容易发现,上面代码时间主要丢失在了1、查找中序中的根节点2、重新组合先序中序队列,所以从这两方面入手优化即可。
优化方案
1、优化查找
1、c++中的map容器,内部采用红黑树实现,内部是有序的,很多操作比如插入删除都是对红黑树的操作,在O(lgn)时间复杂度上就可以完成,记住它内部是严格顺序排列的,利用这个就可以对某些操作加速,并且他的操作都是很快速的,就是浪费空间。
2、本题暂时不用map,因为本体是查找,所以unordered_map,内部是Hash表实现,所以在海量查找有优势,但是建立费时。
2、优化分划
1、我为什么要一次又一次重建先序中序呢,我就保持他俩不变,我就用指针移动模拟不就行了
3、整体优化
1、将关键部分私有化,保障安全性。
代码如下:
注:
1、任何边界的确立:先用简单的样例写,再去用复杂的验证
2、
class Solution {//只能应用于节点不重复的情况下
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)
{
this->preorder = preorder;//初始化
for (int i = 0; i < inorder.size(); i++) {
dic[inorder[i]] = i;
}
return building(0, 0, preorder.size() - 1, inorder.size() - 1);//调用私有函数得到结果。
}
private:
//采用的优化方案就是我把先序中序存起来,利用指针模拟分划即可!!!!!1
vector<int> preorder;//
unordered_map<int, int> dic;//哈希map,查找速度快,但是建立较费时,应用于海量数据查找
//map内部结构规整,利用红黑树实现,具有有序性特点,但是缺点是耗费空间,额外记录父节点孩子节点红黑性质,但操作起来时间复杂度
//再lgN十分快,还有序
TreeNode* building(int left_pre, int left_in, int right_pre, int right_in) {
if (left_pre > right_pre) {
return nullptr;//设计的出口
}
TreeNode* root = new TreeNode(preorder[left_pre]);
int i = dic[preorder[left_pre]];//利用hashmap可以O(1)查到目标
int num_left = i - left_in;//我得知道我左子树有多少节点,便于分划先序队列,这个为啥这么减,你用我上面自己画的简单样例模拟一下就行
root->left = building(left_pre + 1, left_in, left_pre + num_left, i - 1);//左子树部分,中序的右侧明显就是根节点i-1,中序的左侧不用动就行
//左子树部分,前序的左边界根据定义就是现有跟的后一个节点,右边界就是找到现有根,加上左子树个数就行了
root->right = building(left_pre + num_left + 1, i + 1, right_pre, right_in);//右子树部分,中序左边界就是跟的右侧i+1,右边界没变
//右子树部分,先序左边界看左子树右边界加一就行了,因为二者是挨着的,右边界没变
return root;
}
};
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短时间):
(时间复杂度在下方)
时间复杂度: O(N)
Sum Up
1、其实这道题老简单了,就是心中有模型,再加之对先序中序的理解,二者一叠加就能想出来
2、一定要记住优化思路,大量查询unordered_map,变化指针模拟分划,所有范围先看简单的写出来,再用难得验证