1.题目描述:
n个元素{1,2,..., n }有n!个不同的排列。将这n!个排列按字典序排列,并编号为0,1,…,n!-1。每个排列的编号为其字典序值。例如,当n=3时,6 个不同排列的字典序值如下:
0 1 2 3 4 5
123 132 213 231 312 321
任务:给定n 以及n 个元素{1,2,..., n }的一个排列,计算出这个排列的字典序值,以及按字典序排列的下一个排列。
输入:第1 行是元素个数n(n < 15)。接下来的1 行是n个元素{1,2,..., n }的一个排列。
8
2 6 4 5 8 1 7 3
输出:第一行是字典序值,第2行是按字典序排列的下一个排列。
8227
2 6 4 5 8 3 1 7
解题思路:
1.先弄清除字典序的基本概念:如百度百科所解释:
在数学中,字典或词典顺序(也称为词汇顺序,字典顺序,字母顺序或词典顺序)是基于字母顺序排列的单词按字母顺序排列的方法。 这种泛化主要在于定义有序完全有序集合(通常称为字母表)的元素的序列(通常称为计算机科学中的单词)的总顺序。
对于数字1、2、3......n的排列,不同排列的先后关系是从左到右逐个比较对应的数字的先后来决定的。例如对于5个数字的排列 12354和12345,排列12345在前,排列12354在后。按照这样的规定,5个数字的所有的排列中最前面的是12345,最后面的是 54321。
相当于对全排列问题所得到的排列序列进行编号就获得了字典序
2.字典序的序号值的求解:双重循环,第一重循环遍历输入的数组的元素i,第二重循环遍历i+1后面的元素,并且记录比i小的元素(用smaller记录)有几个然后rank+=smaller*(n-i-1)!
3.所给的字典排列的下一个排列:首先必须明白下一个排列的总体一定是比上一个排列的总体大一点点的。从所给的数组从末尾往前寻找找到第一个不满足a[i]>=a[i+1]的i值,并且进行记录。然后在遍历元素从末尾找到第一个比前面记录的a[i]大的j,进行a[i]与a[j]的元素交换,然后翻转i后面的元素使其变为升序
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int factorial(int n) {
int res = 1;
for (int i = 2; i <= n; i++) {
res *= i;
}
return res;
}
int permutationRank(vector<int>& nums) {
int n = nums.size();
vector<int> factorials(n);
for (int i = 0; i < n; i++) {
factorials[i] = factorial(i);
}
int rank = 0;
for (int i = 0; i < n; i++) {
int smaller = 0;
for (int j = i + 1; j < n; j++) {
if (nums[j] < nums[i]) {
smaller++;
}
}//寻找i+1后面有几个元素小于i就smaller++然后去计算rank
rank += smaller * factorials[n - i - 1];
}
return rank;
}
void nextPermutation(vector<int>& nums) {
int n = nums.size();
int i = n - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}//从倒数第二个开始找到第一个不满足升序的位置
if (i >= 0) {
int j = n - 1;
while (j > i && nums[j] <= nums[i]) {
j--;
}
swap(nums[i], nums[j]);
}//从元素末尾找到第一个比i大的元素,然后进行交换
reverse(nums.begin() + i + 1, nums.end());//使i后面的元素变成升序序列
}
int main() {
int n;
cin>>n;
vector<int> nums ;
for(int i=0;i<n;i++)
{
int x;
cin>>x;
nums.push_back(x);
}
int rank = permutationRank(nums);
cout <<rank << endl;
nextPermutation(nums);
for (int i=0;i<n;i++) {
cout <<nums[i]<< " ";
}
cout << endl;
return 0;
}