前言
今天做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;
}