排列組合(permutation)系列解題報告
本文講解4道關於permutation的題目:
1. Permutation:輸出permutation——基礎遞迴
2. Permutation Sequence: 輸出字典序排列的第k個permutation——推理
3. Next Permutation:給定一個permutation中的序列,求字典序它的下一個permutation是什麼——邏輯推理
4. Permutation II:和第一題有細微的差別: 對於一個可能有重複元素的陣列輸出所有permutation——有條件dfs
——基礎遞迴
class Solution{ private: vector<vector<int> > L; vector<int> Nums; int l; vector<bool> visited; public: void perm(vector<int>& list){ if(list.size()==l){ L.push_back(list); return; } for(int i=0; i<l; i++){ if(!visited[i]){ list.push_back(Nums[i]); visited[i] = true; perm(list); list.pop_back(); visited[i] = false; } } } vector<vector<int> > permute(vector<int> &num){ int i; Nums = num; l = Nums.size(); for(i=0;i<l;i++) visited.push_back(false); vector<int>list; perm(list); //for(i=0;i<L.size();i++){ // for(int j = 0;j<l;j++) // cout<<L[i][j]; // cout<<endl; //} return L; } };
——邏輯推理
醬想,n位的permutation有n!個,那麼第k個permutation如果滿足n!<k<(n+1)!就一定有,
a = k/n!
b = k%n!
取集合裡的第a位做下一位,下一次分析剩下的字元組成的第b個permutation
------------------------
e.g. 求[1,2,3,4]組合的第10個
①求[1,2,3,4]組合的第10個
10/3! = 1…4 --->找到[1,2,3,4]中第(1+1)個數(2)做下一位,留下[1,3,4]
②求[1,3, 4]組合的第10-3! * 1 = 4個
4/2! = 2…0 --->找到[1,3,4]中第2個數(3)做下一位, 留下[1,4]
③餘零,說明是permutation裡的最後一個 -> 剩下的逆序輸出
--->2341
class Solution { public: string getPermutation(int n, int k) { int i,j,sum = 1; //sum = (n-1)! for (i=2; i<n; i++) { sum *= i; } bool visited[n+2]; memset(visited, false, sizeof(visited)); string str; for(i=1;i<n;i++){ int nextidx = k/sum; k = k%sum; if(k==0) nextidx -- ; sum/=(n-i); int cnt = 0; for (j=0; j<n; j++) { if (!visited[j]) { if (cnt == nextidx){ visited[j] = true; str += '0' +j+1; break; } cnt ++; } } } for(j=n-1;j>=0;j--){ if (!visited[j]) { str += '0' + j+1; } } return str; } };
給定一個permutation中的序列,求字典序它的下一個permutation是什麼。
——邏輯推理
可以發現,下一個permutation可以這麼得來:
①當前permutation從後往前找到一直上升的子序列,假如一直上升到index_i
②找到index為i到end中最小的,比num[i-1]大的數字,記index為j,交換num[i-1],num[j]
③對num[i]~num[end]從小到大排序
PS:要注意有重複元素的情況e.g {1,5,1};
code:
class Solution {
public:
void nextPermutation(vector<int> &num) {
size_t n = num.size();
int i = (int)n-1;
int j=0;//find the position that stops increasing from tail
while(num[i]<=num[i-1] && i>0)
i--;
sort(num.begin()+i, num.end());
//find the digit that substitute(swap with) i
for(j=i;j<n;j++){
if (num[j]>num[i-1]) {
break;
}
}
if(i>0 && j<n)
swap(num[i-1], num[j]);
}
};
和第一題有細微的差別: 對於一個可能有重複元素的陣列輸出所有permutation。
——有條件dfs
想一下遞迴條件:
肯定還是遞迴,遞迴條件應該是如果當前list中已經出現過這幾個元素排列,就不要再加進去。
所以在第一題基礎上只加兩點:
1)對數組裡所有元素排序
2)對於上一次加到過list的相同元素(必然是在排序後陣列中與上一個相鄰元素相同的)不要再加
class Solution{
private:
vector<vector<int> > L;
vector<int> Nums;
int l;
vector<bool> visited;
public:
void perm(vector<int>& list){
if(list.size()==l){
L.push_back(list);
return;
}
for(int i=0; i<l; i++){
if(!visited[i]){
list.push_back(Nums[i]);
visited[i] = true;
perm(list);
list.pop_back();
visited[i] = false;
while (i<l && Nums[i+1]==Nums[i]) {
i++;
}
}
}
}
vector<vector<int> > permuteUnique(vector<int> &num){
int i;
Nums = num;
l = Nums.size();
sort(Nums.begin(),Nums.end());
for(i=0;i<l;i++)
visited.push_back(false);
vector<int>list;
perm(list);
return L;
}
};