Given any permutation of the numbers {0, 1, 2,..., N−1}, it is easy to sort them in increasing order. But what if Swap(0, *)
is the ONLY operation that is allowed to use? For example, to sort {4, 0, 2, 1, 3} we may apply the swap operations in the following way:
Swap(0, 1) => {4, 1, 2, 0, 3}
Swap(0, 3) => {4, 1, 2, 3, 0}
Swap(0, 4) => {0, 1, 2, 3, 4}
Now you are asked to find the minimum number of swaps need to sort the given permutation of the first N nonnegative integers.
Input Specification:
Each input file contains one test case, which gives a positive N (≤105) followed by a permutation sequence of {0, 1, ..., N−1}. All the numbers in a line are separated by a space.
Output Specification:
For each case, simply print in a line the minimum number of swaps need to sort the given permutation.
Sample Input:
10
3 5 7 2 6 4 9 0 8 1
Sample Output:
9
题目大意
这题给出0到n-1的一个数字序列,要求只能用0和其他数字进行交换,然后排序成从小到大的序列,求出最小的交换次数。
个人思路
这题是贪心算法的应用,贪心在每次选择交换的都是当前位置编号的那个数字,即每次都尽量让一个数字到达正确位置。
这题我建立了一个映射mapp,存储数字和数字位置的编号,如mapp[num] = i,表示数字num在第i个位置,当i == num的时候表示数字位置正确,否则数字位置错误。
同时还设立了两个set容器,存储位置正确的数字集合和数字错误的数字集合。
程序主要过程在一个循环中,正确数字集合的元素个数等于n时退出循环。
0在和其他数字的交换过程中有两种情况:
第一种是排序尚未完成,但是0已经在位置0处了,此时需要找到一个错误位置的数字把0从正确位置换出来。
第二种情况是0在非正确位置,那么就把0和位置编号的这个数字zero_pos进行交换,即把zero_pos换到了正确位置。
在循环中反复对0进行交换并计数,直到退出循环。
代码实现
#include <cstdio>
#include <set>
#include <cmath>
#include <iostream>
using namespace std;
const int maxn = 100005;
int mapp[maxn]; // 记录数字编号到数字位置的映射
int main() {
// correct记录位置正确的数字编号 wrong记录位置错误的数字编号
set <int> correct, wrong;
// 输入并记录初始正确/错误状态
int n;
scanf("%d", &n);
for (int i = 0; i < n; i ++) {
int num;
scanf("%d",&num);
mapp[num] = i;
if (num == i) correct.insert(num);
else wrong.insert(num);
}
// 当所有的数字编号位置都正确时,结束循环
int cnt = 0;
while (correct.size() < n) {
// 如果数字0在0位置处,需要把0换出来
if (mapp[0] == 0) {
int wrong_num = *(wrong.begin());
swap(mapp[0], mapp[wrong_num]);
correct.erase(0);
wrong.insert(0);
cnt ++;
}
// 将0和0所占的位置编号数进行交换,那么原来的0所占的位置编号数在交换后就到达正确位置了
int zero_pos = mapp[0];
swap(mapp[0], mapp[zero_pos]);
correct.insert(zero_pos);
wrong.erase(zero_pos);
if (mapp[0] == 0) {
correct.insert(0);
wrong.erase(0);
}
cnt ++;
}
printf("%d", cnt);
return 0;
}
总结
学习不息,继续加油
这题说实话修改了好几次。不断优化最终才AC,一开始把0从正确位置换出来的时候寻找错误数字是通过遍历数字序列实现的,后来我干脆直接不存数字序列而直接存数字和下标的映射关系。然后还是有一个样例会超时,于是我把对数字序列的操作给删除了,因为存数字和下标的映射已经够了,就不用存数字序列了。改到这一步其实已经AC了,但是我发现在超时的边缘,有一个样例的用时在195ms+,然后我发现其实不用map存映射关系,用普通的数组就可以了【因为map是红黑树实现,复杂度有O(logn),而数组是随机访问O(1)】,改完之后时间就比较好了。
然后我又去看了看柳神的代码,才发现自己又天真了。其实我发现改到最后我的思路和柳神已经很像了,但是对于一些条件的判断我写得很麻烦【虽然思路看上去比较直观一点】,柳神通过巧妙的思路设计完全没有用STL,二十行代码就解决问题了。这里贴出柳神链接膜一下。https://www.liuchuo.net/archives/2301
#include <iostream>
using namespace std;
int main() {
int n, t, cnt = 0, a[100010];
cin >> n;
for(int i = 0; i < n; i++){
cin >> t;
a[t] = i;
}
for(int i = 1; i < n; i++) {
if(i != a[i]) {
while(a[0] != 0) {
swap(a[0],a[a[0]]);
cnt++;
}
if(i != a[i]) {
swap(a[0],a[i]);
cnt++;
}
}
}
cout << cnt;
return 0;
}