牛客网:全排列
目錄
1.沒有重復數字的全排列
2.有重復數字的全排列
暴力
1.沒有重復數字的全排列
?我們其實也可以用next_permutation這個接口,但是總不如自己寫的有意思
首先,這里的字典序,我也不知道答案怎么實現的,官網給出的答案貌似是根本沒有考慮的。
所以我們在這里也隨便亂序輸出。
我們設置一個函數,這個函數表示現在第k位以前的數字已經不會移動,我們要改變的是位置>=k之后的數字之間的位置關系。因此要傳入一個索引位置作為參數,一個答案的引用用來填寫答案,以及一個原來的數組引用。
至于思路,我們就是暴力完事,當然用遞歸實現:
對于ABC這樣一個序列
首先ABC本身就是一個答案之一,這也是為什么=k取到的原因,=k表示的是自己和自己交換即當前位置不交換。
接下來說點要交換的,比如我們拿A依次和后面的所有元素交換,例如與B,變成BAC,接下來我們就忽略第一位,只看AC,這個AC有AC和CA兩種排列方法,其實意思是A與后面的元素換或不換的兩種情況。
那么我們提取思路就是:
對于一個序列A...,A的位置不變或者和后面的所有元素交換依次,然后在A交換完后切換到下一個位置的元素進行相同的操作,這不就是遞歸么。
代碼如下所示:
class Solution { public:void permuteCore(vector<vector<int>> &res,vector<int> &num, int k){if(k==num.size()-1){res.push_back(num);return;}for(int i=k;i<num.size();++i){swap(num[i], num[k]);permuteCore(res, num, k+1);swap(num[i], num[k]);}}vector<vector<int> > permute(vector<int> &num) {sort(num.begin(), num.end());vector<vector<int>> res;permuteCore(res, num, 0);return res;} };這里要注意回溯的細節。
其次,每個元素循環的次數是n-k,n是總長度,k是下標位置,k=0,1...n,所以時間復雜度是O(n!)
2.有重復數字的全排列
這個時候有重復數字,如果我們還是一樣來交換的話,需要用到集合來做數據結構。
我們這里有兩種方法,用集合(交換),和暴力。
暴力
本質上求取一個全排列其實是決定數字的放置位置,比如第一個位置放第幾個數,第二個位置放第幾個數。因此,我們可以用一個visit數組來記錄,我們之前已經放置了哪些位置的數字。
之后我們要解決重復的問題,比如我們第1個位置放了2,整個數組里面有兩個2,我們如何避免重復?
首先將數組進行排序,這樣很清楚哪些數重復了,其實就是那些num[i-1]==num[i]的數字。
決定第k個位置是否放置數字num[i]的思路如下:
1.如果visit[i]=1說明該位置已經在前面的遞歸中放過,可以直接看下一個數
2.如果出現兩個數字相鄰且前面那個數字visit[i]=0說明已經在前面放過,那么直接跳過當前數,否則就會出現重復的情況
代碼如下所示:
class Solution { public:void permuteUniqueCore(vector<vector<int>> &res, vector<int> &num, vector<int> &tmp, vector<int> &visit){if(tmp.size()==num.size()){res.push_back(tmp);return;}for(int i=0;i<num.size();++i){if(visit[i] || (i>0 && num[i-1]==num[i] && !visit[i-1])){continue;}visit[i]=1;tmp.push_back(num[i]);permuteUniqueCore(res, num, tmp, visit);visit[i]=0;tmp.pop_back();}}vector<vector<int> > permuteUnique(vector<int> &num) {sort(num.begin(), num.end());vector<vector<int>> res;vector<int> tmp;vector<int> visit(num.size(), 0);permuteUniqueCore(res, num, tmp, visit);return res;} };集合
之前說過如果直接在原數組上交換的話,直接用集合來存儲就好了,代碼如下所示:
#include <vector> #include <algorithm> #include <set> using namespace std; class Solution { public:void permuteUniqueCore(set<vector<int>> &st, vector<int> &num, int k){if(k==num.size()){st.insert(num);return;}for(int i=k;i<num.size();++i){swap(num[i],num[k]);permuteUniqueCore(st, num, k+1);swap(num[i],num[k]);}}vector<vector<int> > permuteUnique(vector<int> &num) {set<vector<int>> st;sort(num.begin(), num.end());vector<int> tmp;permuteUniqueCore(st, num, 0);vector<vector<int>> res;for(auto vec : st){res.push_back(vec);}return res;} };next_permutation接口
當然,還有一個神奇的接口可以調用,那就是next_permutation,這個接口就是喂給他一個序列然后他會給你下一個剛好大一點的排列序列,寫法如下所示:
using namespace std; class Solution { public: vector<vector<int> > permuteUnique(vector<int> &num) {sort(num.begin(), num.end());vector<vector<int>> res;res.push_back(num);while(next_permutation(num.begin(), num.end())){res.push_back(num);}return res;} };我們也可以自己手寫一個permutation:
bool next_Permutation_Own(vector<int> &num){int i=num.size()-2;//從倒數第二個元素往前找到第一個元素//這個元素比其后面的那個元素要小//或者直到第一個while(i>=0 && num[i] >= num[i+1]){i--;}int j=num.size()-1;//從最后一個元素向前找第一個大于位置i的元素的元素位置while(j>=0 && num[i]>=num[j]){j--;}swap(num[i], num[j]);reverse(num.begin()+i+1, num.end());return true;}思路是這樣的:
從后往前找到第一個非遞增元素的位置,從后往前找到第一個大于這個元素的另外一個元素的位置,這樣交換后就比剛才的大了一點,至于為什么要reverse一下,因為reverse之后更小了,我只能這么理解,至于怎么證的屬實不懂。
總結
- 上一篇: 任意多边形面积计算
- 下一篇: 盈一份恬淡,安然一世春秋!