题目如下:
将 { 0, 1, 2, ..., N-1 } 的任意一个排列进行排序并不困难,这里加一点难度,要求你只能通过一系列的 Swap(0, *) —— 即将一个数字与 0 交换 —— 的操作,将初始序列增序排列。例如对于初始序列 { 4, 0, 2, 1, 3 },我们可以通过下列操作完成排序:
- Swap(0, 1) ⟹ { 4, 1, 2, 0, 3 }
- Swap(0, 3) ⟹ { 4, 1, 2, 3, 0 }
- Swap(0, 4) ⟹ { 0, 1, 2, 3, 4 }
本题要求你找出将前 N 个非负整数的给定排列进行增序排序所需要的最少的与 0 交换的次数。
输入格式:
输入在第一行给出正整数 N (≤105);随后一行给出{ 0, 1, 2, ..., N-1 } 的一个排列。数字间以空格分隔。
输出格式:
在一行中输出将给定序列进行增序排序所需要的最少的与 0 交换的次数。
输入样例:
10
3 5 7 2 6 4 9 0 8 1
输出样例:
9
根据题意以该位置的数值进行交换,时间复杂度为O(n^2),会超时,代码1如下:
#if 1
#include <iostream>
using namespace std;
const int maxn=100005;
int c_time(int *a,int n,int t)
{
int cnt=0;
while(1)
{
if(t!=0)
{
for(int i=0;i<n;i++)
{
if(a[i]==t)
{
swap(a[t],a[i]);
t=i;
cnt++;
break;
}
}
}
else
{
int t_num=0,j=0;
for(int i=0;i<n;i++)
{
if(a[i]!=i)
{
swap(a[t],a[i]);
t=i;
cnt++;
break;
}
}
if(t==0) return cnt;
}
}
}
int a[maxn];
int main()
{
int n,t; scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
if(a[i]==0) t=i;
}
cout<<c_time(a,n,t)<<endl;
return 0;
}
#endif
根据数值下标去交换,时间复杂度为O(n)代码2如下:
#if 1
#include <iostream>
using namespace std;
const int maxn=1e5+5;
int circle(int *a,bool *b,int i)
{
int t=0,flag=0;
while(!b[i])
{
if(i==0) flag=1;
t++;
b[i]=true;
i=a[i];
}
if(flag) return t-1;
else return t+1;
}
int a[maxn];
bool b[maxn];
int main()
{
int n; scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",a+i);
if(i==a[i]) b[i]=true;
}
int sum=0;
for(int i=0;i<n;i++)
{
if(!b[i]) sum+=circle(a,b,i);
}
cout<<sum<<endl;
return 0;
}
#endif
代码1和代码2的思路互为相反。本质上,是有向环,因此,环的开头是谁并不重要,但只能有一个开头,环于环时互不相交的。另外,有向环中包含0时,实际次数为t-1(0在其中,正常交换,环的节点数t减1);不包含0时,实际次数为t+1(在正常交换的基础上(t-1次),0要进去,也要出来,所以变成(t+1))。以上。