题目描述
难度分:1500
输入n(2≤n≤2×105)和长为n的数组a(1≤a[i]≤n)。
最少要修改多少个元素,使得a是一个1~n的排列?
输出最少修改次数,以及修改后的a。
如果有多个a符合要求,输出字典序最小的a。
输入样例1
4
3 2 2 3
输出样例1
2
1 2 4 3
输入样例2
6
4 5 6 3 2 1
输出样例2
0
4 5 6 3 2 1
输入样例3
10
6 8 4 6 7 1 6 3 4 5
输出样例3
3
2 8 4 6 7 1 9 3 10 5
算法
贪心
这个题的第一要义是操作次数少,因此我们用一个数组cnt统计一下a中每个数值的频数,对于数值val,如果它的频数freq=cnt[val]超过1,那么有freq−1个val就都是要改成其他数的,因此操作次数就是freq大于1的所有freq−1之和(同样也是[1,n]内缺少的那些数的个数)。
接下来贪心地来进行替换,先将[1,n]中所有缺少的数存入not_used数组(保持有序),然后遍历原数组a,对于a[i],替换与否分以下三种情况来讨论:
- 如果a[i]的频数超过1,那显然a[i]是有可能被替换的,此时如果not_used中的最小数小于a[i],那就果断用这个最小数将a[i]替换掉,并更新a[i]的频数(自减)。
- 如果a[i]已经出现过了(这里可以用一个used数组来标记每个数是否出现过),此时也需要用not_used中的最小数把a[i]替换掉。
- 否则不替换,a[i]仍然保持原值。
复杂度分析
时间复杂度
统计各个数值的频数,构建not_used数组都需要遍历一遍a数组,时间复杂度为O(n)。接下来贪心地替换a[i]也是遍历i∈[1,n],由于可以让not_used数组保持有序,能够在O(1)的时间复杂度下获得最小值,时间复杂度仍然为O(n)。因此,算法整体的时间复杂度为O(n)。
空间复杂度
空间瓶颈在于频数数组cnt和not_used数组,都是O(n)规模的,因此额外空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 200010;
int n, a[N], cnt[N], used[N];
int main() {
scanf("%d", &n);
memset(cnt, 0, sizeof cnt);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
cnt[a[i]]++;
}
memset(used, 0, sizeof used);
vector<int> not_used;
for(int i = 1; i <= n; i++) {
if(!cnt[i]) not_used.push_back(i);
}
printf("%d\n", (int)not_used.size());
int j = 0;
for(int i = 1; i <= n; i++) {
if(cnt[a[i]] >= 2 && not_used[j] < a[i]) {
int t = not_used[j];
printf("%d ", not_used[j++]);
cnt[a[i]]--;
used[t]++;
}else if(used[a[i]]) {
used[not_used[j]]++;
printf("%d ", not_used[j++]);
}else {
used[a[i]]++;
printf("%d ", a[i]);
}
}
return 0;
}