题目链接在此。
题意理解
给定有N个数字的序列(数字的范围是0-N-1),通过任意数和0进行交换,使之最后递增排好序,求最少的交换次数。
思路
很容易用到贪心的思想,首先想到了就是通过交换0和若排好序后应该在0所在位置处的元素进行交换(比如,0在3号位(从0号位开始),应该用4和0进行交换),这样可以一次交换就确定好一个数的位置,可以求得最小次数。
然后发现,题干中给的N=5的样例的确可以得到正确的结果。但是题目给的样例1就不行了。因为当进行完第三次上述交换之后,0已经在0号位,这时循环就退出了,但并没有排好序。
那么当出现这种情况的时候,应该采取什么样的策略使循环能够继续下去呢?很显然,由于我们只能交换0和另外一个数,而又需要将0从0号位拿走,那么必须找到一个数和0进行交换,这个数可以是任意一个没有最终“归位”的数。很显然这种策略是最“贪心”的,在必须用0和其他数交换的情况下选择未“归位”的数,这对前面已经确定好位置的数已经后面需要继续确定位置的数是没有影响的,所以最优。
为了确定总的循环何时退出,定义一个left变量,用来保存除0以外的没有“归位”的元素的个数,这样当left==0时可以退出循环,说明序列已经有序。
代码探究
提交的第一个版本的代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
void swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
int main(){
int n;
scanf("%d",&n);
int a[n];
int pos; //记录0所在下标
int left = n-1; //除0以外不在本位的数的个数
for(int i = 0; i < n; i++){
scanf("%d",&a[i]);
if(a[i] == 0){
pos = i;
}
if(a[i] != 0 && a[i]==i){ //i在本位(除0以外)
left--;
}
}
int count = 0;
while(left > 0){
while(a[0] != 0){
int i;
for(i = 0; i < n; i++){ //找到排好序后应该在0所在位置的元素的下标
if(a[i] == pos){
break;
}
}
swap(a[i],a[pos]);
left--;
count++;
pos = i; //更新0所在下标
}
if(a[0] == 0){ //此时还是无序,则随便拿一个没回到“本位”的数交换
int i;
for(i = 0; i < n; i++){
if(a[i] != i){ //找到还未回到“本位”的数
swap(a[0],a[i]);
pos = i;
count++;
break;
}
}
}
}
printf("%d\n",count);
return 0;
}
这个版本的代码交上去是有两个测试点超时的。从代码上分析可以知道,时间复杂度主要集中在两个地方:
1. “0所在位置”本该有的元素的查找
2. 当0已经在第0位时,查找第一个未“归位”的元素
那么就需要对这两个部分进行改进了。
第2点的修改要容易一些,这一点的时间浪费主要在每次都从0开始找第一个“未归位”的元素,那么可以定义一个全局的变量k(初值为1),用k来保存最小的未归位的元素,下次就可以从k开始,而不是从0开始。
但是修改之后,两个测试点还是超时了,看来必须对第1点进行修改了。也就是说,怎样可以不遍历a[]数组,而是用更有效率的方法确定“0所在位置”本该有的元素。
这里对数组a[]的作用进行改进,定义成a[i]=j表示i这个数在序列中的位置,那么0所在的位置就是a[0],本该在0所在位置的元素的位置就是a[a[0]]。这样一想,其实交换的不是真实的两个数,而是两个数所在的位置。
AC代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
void swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
int main(){
int n;
scanf("%d",&n);
int a[n]; //a[i] = j 表示数字i在序列中的下标
int left = n-1; //除0以外不在本位的数的个数
int temp;
for(int i = 0; i < n; i++){
scanf("%d",&temp);
a[temp] = i;
if(temp != 0 && temp == i){ //i在本位(除0以外)
left--;
}
}
int count = 0;
int k = 1; //不在本位上的最小的数
while(left > 0){
while(a[0] != 0){
swap(a[0],a[a[0]]);
left--;
count++;
}
if(a[0] == 0){ //此时还是无序,则随便拿一个没回到“本位”的数交换
int i;
for(i = k; i < n; i++){
if(a[i] != i){ //找到还未回到“本位”的数
swap(a[0],a[i]);
count++;
break;
}
}
k = i+1;
}
}
printf("%d\n",count);
return 0;
}