前言
今天做LeetCode里面的关于全排列的题目:31. Next Permutation发现自己对全排列是完全不了解。所以狠搜一波。总结一些知识点。(PS:该总结中待排列元素假定为整型数字。由于个人习惯,right通常指左边的元素,left通常指相对右的元素);
下一个排列(next permutation)
算法1:
代码:
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int i = nums.size() - 1;
while(i > 0 && nums[i] <= nums[i-1])
i--;
i--;
if(i >= 0)
{
int j = nums.size() - 1;
while(nums[i] >= nums[j]){
j--;
}
swap(nums[i], nums[j]);
sort(nums.begin() + i+1, nums.end());//反转操作可以使用排序完成,因为right后面的元素均为逆序
}else{
sort(nums.begin(), nums.end());//同上
}
}
};
上一个排列(pre_permutation)
给定一个序列,求该序列在全排列序列中前一个排序。算法和下一个排列很相近,操作差不多,将right<left改成right>left。然后第二个操作改成找到以一个小于right的数交换。接下来操作相同。直接看代码!
bool prePermutation(vector<int> &nums)
{
int size = nums.size();
for (int i = size - 1; i > 0; --i)
{
if (nums[i] < nums[i - 1])
{
int val = nums[i - 1];
int j = size - 1;
for (; j >= i; --j)
{
if (nums[j] < val)
break;
}
swap(nums[i - 1], nums[j]);
int l = i;
int r = size - 1;
while (l < r)
{
swap(nums[l], nums[r]);
l++;
r--;
}
return true;
}
}
int start = 0;
int last = size - 1;
while (start < last)
{
swap(nums[start], nums[last]);
start++;
last--;
}
return false;
}
第n个排列(n'st Permutation)
当给定一系列元素,(有无重复元素是否有影响?)直接给出全排列序列中的第n个。不能从第一个生成至第n个。
无重复元素求第n个排列有一个方法——康拓展开。
公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0!;
操作说明: {1,2,3,4,5} 的第16个排列。
分析:第16个排列说明该排列前面有15排列。
1. 15/(5-1)! = 0 ......15 即第一位数字在数字集合中有0个数字比它小=> 1
2. 取上一次操作的余数15/(5-2)! = 2 ......3 即第二位数字在数字集合中有2个比它小 => 4(1已经取出)
3. 3/(5-3)! = 1 ......1 => 3
4. 1/(5-4)! = 1 ......0 => 5
5. 最后剩下2
总结得 排列为 {1,4,3,5,2}。
代码:
int fac[] = {1,1,2,6,24,120,720,5040,40320};//阶乘提前处理!
//康托展开的逆运算,{1...n}的全排列,中的第k个数为s[]
void reverse_kangtuo(int n,int k,char s[])
{
int i, j, t, vst[8]={0};
--k;
for (i=0; i<n; i++)
{
t = k/fac[n-i-1];
for (j=1; j<=n; j++)
if (!vst[j])
{
if (t == 0) break;
--t;
}
s[i] = '0'+j;
vst[j] = 1;
k %= fac[n-i-1];
}
}
排列是第几?(index of permutatioin)
当知道一系列元素全排列中的某一个排列,想知道这个排序是第几个排列。如果使用next_permutation从第一个计算这时复杂度为O(n*n)。可以使用康拓展开来计算,第几个排列使用的是康拓展开的逆。 操作示例:{1,2,3,4,5} 中的一个排列{1,4,3,5,2}。
1. 第一个1在集合中有0个比他小,则index+=0*(5-1)! => index = 0;
2. 第二个4在集合中有2个比它小, 则index+=2*(5-2)! => index = 12;
3. 第三个3在集合中有1个比它小, 则index+=1*(5-3)! => index = 14;
4. 第四个5在集合中有1个比它小, 则index+=1*(5-4)! => index = 15;
5. 第五个为最后一个为0.
总结:上面逆的使用时做减一操作,这里做加一操作。即index ++ =>index = 16。 说明{1,4,3,5,2}是{1,2,3,4,5}的全排列中的第16个排列。
代码:
int fac[] = {1,1,2,6,24,120,720,5040,40320}; //i的阶乘为fac[i]
// 康托展开-> 表示数字a是 a的全排列中从小到大排,排第几
// n表示1~n个数 a数组表示数字。
int kangtuo(int n,char a[])
{
int i,j,t,sum;
sum=0;
for( i=0; i<n ;++i)
{
t=0;
for(j=i+1;j<n;++j)
if( a[i]>a[j] )
++t;
sum+=t*fac[n-i-1];
}
return sum+1;
}