修改数组

修改数组

给定一个长度为 N 的数组 A=[A1,A2,AN],数组中有可能有重复出现的整数。

现在小明要按以下方法将其修改为没有重复整数的数组。

小明会依次修改 A2,A3,,AN

当修改 Ai 时,小明会检查 Ai 是否在 A1Ai1 中出现过。

如果出现过,则小明会给 Ai 加上 1;如果新的 Ai 仍在之前出现过,小明会持续给 Ai1,直到 Ai 没有在 A1Ai1 中出现过。

AN 也经过上述修改之后,显然 A 数组中就没有重复的整数了。

现在给定初始的 A 数组,请你计算出最终的 A 数组。

输入格式

第一行包含一个整数 N

第二行包含 N 个整数 A1,A2,,AN

输出格式

输出 N 个整数,依次是最终的 A1,A2,,AN

数据范围

1N105,
1Ai106

输入样例:

5
2 1 1 3 4

输出样例:

2 1 3 4 5

 

解题思路

解法一:并查集

  这是一个另类并查集,有点像单链表。

  fa[i]表示单链表中的下一个结点。i所在的这颗树(集合)的根节点(代表元素)是从i开始向右找,第一个没有被用过的数字。也就是说,在这个集合中,只有根节点这个数字是没有被用过的,其余的数字都被用过。i的下一个元素(即fa[i])可能不是根节点,也可能是根节点,但通过路径压缩最后一定会指向根节点。每次用了根节点后,都把根节点并到根节点所代表的数字的下一个数字。

  根据样例模拟一下:

  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 }
复制代码

  这种并查集的更一般用法:删除区间问题,每次删除区间[L,R]内的数,每个数都只会删一次,但可能会重复查询某个区间,问某个数是第几次删除的。

  如果删除区间[L,R],那就将该区间中的每个数xp[x]设置成R+1。对于某个x,如果find(x)==x,就表示该数未被删除,否则就表示该数已被删除。

 

解法二:平衡树

  这里的平衡树是用std::set来实现的。

  用平衡树来维护连续的区间,这些区间中的数都是已经使用过的。每次遍历到一个数x,就查看这个数是否出现在某一个区间中,如果出现在某一个区间[L,R]中,那么这个数只能通过加1加到R+1,把x变为R+1,否则就是x。然后把变化后的x插入到这些区间中,因为加入了x后某些区间就相邻了,因此要更新区间,把相连的两个区间合并到一个区间,比如把[L,k][k+1,R]合并成[L,R]

  如何找到x是否出现在某一个区间中呢?我们只需要遍历区间的右端点,找到右端点大于等于x的第一个区间,然后判断这区间的左端点是否小于等于x,如果是,那么x就在这个区间中,否则不再。这里还有一个细节是,我们是用std::set::lower_bound()来找这个区间,因此我们用pair来存储左右区间的端点,但first用来存储右端点,second来存储左端点。

  接下来就是合并区间的操作。我们确定了x更新成某个数后,是直接先把[x,x]这个区间插入平衡树的,可以发现最多会有两次区间的合并,也就是这种情况[L,x1][x,x][x+1,R]。也有可能只合并一次,或不合并。如果发现前一个区间的右端点加上1后等于后一个区间的左端点,那么就合并这两个区间。

  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/

posted @   onlyblues  阅读(143)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
历史上的今天:
2021-03-14 一元多项式的乘法与加法运算
Web Analytics
点击右上角即可分享
微信分享提示