修改数组
修改数组
给定一个长度为 的数组 ,数组中有可能有重复出现的整数。
现在小明要按以下方法将其修改为没有重复整数的数组。
小明会依次修改 。
当修改 时,小明会检查 是否在 中出现过。
如果出现过,则小明会给 加上 ;如果新的 仍在之前出现过,小明会持续给 加 ,直到 没有在 中出现过。
当 也经过上述修改之后,显然 数组中就没有重复的整数了。
现在给定初始的 数组,请你计算出最终的 数组。
输入格式
第一行包含一个整数 。
第二行包含 个整数 。
输出格式
输出 个整数,依次是最终的 。
数据范围
,
输入样例:
5 2 1 1 3 4
输出样例:
2 1 3 4 5
解题思路
解法一:并查集
这是一个另类并查集,有点像单链表。
表示单链表中的下一个结点。所在的这颗树(集合)的根节点(代表元素)是从开始向右找,第一个没有被用过的数字。也就是说,在这个集合中,只有根节点这个数字是没有被用过的,其余的数字都被用过。的下一个元素(即)可能不是根节点,也可能是根节点,但通过路径压缩最后一定会指向根节点。每次用了根节点后,都把根节点并到根节点所代表的数字的下一个数字。
根据样例模拟一下:
AC代码如下:
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 const int N = 1e5 + 1e6 + 10; 6 7 int fa[N]; 8 9 int find(int x) { 10 return fa[x] == x ? fa[x] : fa[x] = find(fa[x]); 11 } 12 13 int main() { 14 int n; 15 scanf("%d", &n); 16 17 for (int i = 1; i < N; i++) { 18 fa[i] = i; 19 } 20 21 while (n--) { 22 int val; 23 scanf("%d", &val); 24 val = find(val); // 找根节点,根节点没有用过 25 fa[val] = val + 1; // 根节点已经被用过了,把根节点并到下一个数字 26 27 printf("%d ", val); 28 } 29 30 return 0; 31 }
这种并查集的更一般用法:删除区间问题,每次删除区间内的数,每个数都只会删一次,但可能会重复查询某个区间,问某个数是第几次删除的。
如果删除区间,那就将该区间中的每个数的设置成。对于某个,如果,就表示该数未被删除,否则就表示该数已被删除。
解法二:平衡树
这里的平衡树是用std::set来实现的。
用平衡树来维护连续的区间,这些区间中的数都是已经使用过的。每次遍历到一个数,就查看这个数是否出现在某一个区间中,如果出现在某一个区间中,那么这个数只能通过加加到,把变为,否则就是。然后把变化后的插入到这些区间中,因为加入了后某些区间就相邻了,因此要更新区间,把相连的两个区间合并到一个区间,比如把和合并成。
如何找到是否出现在某一个区间中呢?我们只需要遍历区间的右端点,找到右端点大于等于的第一个区间,然后判断这区间的左端点是否小于等于,如果是,那么就在这个区间中,否则不再。这里还有一个细节是,我们是用std::set::lower_bound()来找这个区间,因此我们用pair来存储左右区间的端点,但first用来存储右端点,second来存储左端点。
接下来就是合并区间的操作。我们确定了更新成某个数后,是直接先把这个区间插入平衡树的,可以发现最多会有两次区间的合并,也就是这种情况,,。也有可能只合并一次,或不合并。如果发现前一个区间的右端点加上后等于后一个区间的左端点,那么就合并这两个区间。
AC代码如下:
1 #include <cstdio> 2 #include <set> 3 #include <algorithm> 4 using namespace std; 5 6 typedef pair<int, int> PII; 7 8 int main() { 9 int n; 10 scanf("%d", &n); 11 12 set<PII> st; 13 while (n--) { 14 int val; 15 scanf("%d", &val); 16 17 auto t = st.lower_bound({val, -1}); // 找到右端点大于等于val的第一个区间 18 if (t != st.end() && t->second <= val) val = t->first + 1; // 这个区间要存在,且区间的左端点小于val说明val在这个区间中,val变成右端点的值加1 19 20 t = st.insert({val, val}).first; // 插入[val, val]这个区间,val被用过了,同时记录这个区间的迭代器 21 if (t != st.begin()) t--; // 如果这个区间不是第一个区间,说明前面存在一个区间,这个区间也许会与[val, val]合并,因此让迭代器指向这个区间 22 23 for (int i = 0; i < 2 && t != st.end(); i++) { // 最多进行两次区间合并 24 auto j = t; // t指向当前区间 25 j++; // j指向当前区间的下一个区间 26 27 // 如果这个区间存在,且当前区间的右端点加1后等于下一个区间的左端点,则把这两个区间合并 28 if (j != st.end() && t->first + 1 == j->second) { 29 int l = t->second, r = j->first; // 记录合并后的新区间的左右端点的值 30 st.erase(t), st.erase(j); // 把原来的两个区间删除 31 t = st.insert({r, l}).first; // 插入新区间,右端点为第一关键字,左端点为第二关键字,同时获得新区间的迭代器 32 } 33 // 否则这两个区间不可以合并 34 else { 35 t++; // 当前区间变成下一个区间 36 } 37 } 38 39 printf("%d ", val); 40 } 41 42 return 0; 43 }
参考资料
AcWing 1242. 修改数组(蓝桥杯C++ AB组辅导课):https://www.acwing.com/video/799/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16005755.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
2021-03-14 一元多项式的乘法与加法运算