题目描述:
算法思想:
(1)求排列的字典序值
-
依次处理每一个下标i(i=0,1,2…n-1)
-
统计小于a[i]且未使用过的数的个数m
-
计算阶乘:m*(n-i-1)!
-
求和得到字典序
看一个例子
a{2 6 4 5 8 1 7 3}
i 0 1 2 3 4 5 6 7
sum=0;
i=0 比2小的数有1, 则 sum+=1*(8-0-1)!;
i=1 比6小的数有2,3,4,5 则 sum+=4*(8-1-1)!;
i=2 比4小的数有1,3 则 sum+=2*(8-2-1)!;
i=3 比5小的数有1,3, 则 sum+=2*(8-3-1)!;
i=4 比8小的数有1,3,7 则 sum+=3*(8-4-1)!;
i=5 比1小的数没有 则 sum+=0*(8-5-1)!;
i=6 比7小的数有3 则 sum+=1*(8-6-1)!;
i=7 比3小的数没有 则 sum+=0*(8-7-1)!;
(2)求按字典序排列的下一个排列
1 从尾部往前找第一个a(i) < a(i+1)的位置
2 6 4 4 5 8 1 <-- 7 <-- 3
最终找到1是第一个变小的数字,记录下1的位置i
2 从尾部往前找到第一个大于1的数
2 6 4 4 5 8 1 7 3 <--
最终找到3的位置,记录位置为j
3 交换位置i和j的值
2 6 4 4 5 8 3 7 1
4 倒序i位置后的所有数据
2 6 4 4 5 8 3 1 7
这样做的原因:
首先更改排列必然意味着需要交换两个数的位置(这是个排列问题)
为什么从尾部往前找:交换的两个数越是靠近尾部则与交换前的字典序差值越小
为什么找第一个a(i-1) < a(i)的位置:交换一个递减序列中的两个值,即小数前移,字典序一定减小
为什么找第一个大于a[i-1]的数:大数前移,字典序一定增大,且前移的数是尾部满足条件中最小的,字典序增大的范围也是最小的。
为什么倒序i位置后的所有数据:交换完后i位置后的所有数据仍然为一个递减序列,颠倒后成为一个递增序列,即为a[0]~a[i]固定后的最小字典序排列
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define N 100
int n,a[N],f[N];
//数组逆序
void reverse(int list[],int l,int r){
int i=l,j=r;
int temp;
while(i<j){
temp=a[i];
a[i]=a[j];
a[j]=temp;
i++;
j--;
}
}
int main(){
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
scanf("%d",&n);
f[0]=1;
for(int i=0;i<n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) f[i]=f[i-1]*i;
//求字典序值:
int sum=0;
for(int i=0;i<n;i++){
int m=0;
//找序列中比a[i]小且前面未出现过的数
for(int j=i;j<n;j++){
if(a[j]<a[i]){
m++;
}
}
sum+=m*f[n-i-1];
}
//求下一个排列
for(int i=n-2;i>=0;i--){
//从尾部往前找第一个a(i-1) < a(i)的位置
if(a[i]<a[i+1]){
for(int j=n-1;j>i;j--){
if(a[j]>a[i]){
swap(a[i],a[j]);
break;
}
}
reverse(a,i+1,n-1);
break;
}
}
printf("%d\n",sum);
for(int i=0;i<n;i++) printf("%d",a[i]);
return 0;
}