Next Permutation

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.html

If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).算法

The replacement must be in-place, do not allocate extra memory.數組

Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1函數

思路:spa

如下分析轉載自http://www.cnblogs.com/devymex/archive/2010/08/17/1801122.htmlcode

概念

全排列的生成算法有不少種,有遞歸遍例,也有循環移位法等等。但C++/STL中定義的next_permutation和prev_permutation函數則是很是靈活且高效的一種方法,它被普遍的應用於爲指定序列生成不一樣的排列。本文將詳細的介紹prev_permutation函數的內部算法。htm

按照STL文檔的描述,next_permutation函數將按字母表順序生成給定序列的下一個較大的序列,直到整個序列爲減序爲止。prev_permutation函數與之相反,是生成給定序列的上一個較小的序列。兩者原理相同,僅遍例順序相反,這裏僅以next_permutation爲例介紹算法。blog

下文內容都基於一個假設,即序列中不存在相同元素。對序列大小的比較作出定義:兩個長度相同的序列,從二者的第一個元素開始向後尋找,直到出現一個不一樣元素(也可能就是第它們的第一個元素),該元素較大的序列爲大,反之序列爲小;若一直到最後一個元素都相同,那麼兩個序列相等。遞歸

設當前序列爲pn,下一個較大的序列爲pn+1,這裏蘊藏的含義是再也找不到另外的序列pm,使得pn < pm < pn+1文檔

 

問題

給定任意非空序列,生成下一個較大或較小的序列。

 

數學推導

根據上述概念易知,對於一個任意序列,最小的序列是增序,最大的序列爲減序。那麼給定一個pn要如何才能生成pn+1呢?先來看下面的例子:

咱們用<a1 a2 ... am>來表示m個數的一種序列。設序列pn=<3 6 4 2>,根據定義可算得下一個序列pn+1=<4 2 3 6>。觀察pn能夠發現,其子序列<6 4 2>已經爲減序,那麼這個子序列不可能經過交換元素位置得出更大的序列了,所以必須移動最高位3(即a1)的位置,且要在子序列<6 4 2>中找一個數來取代3的位置。子序列<6 4 2>中6和4都比3大,但6大於4。若是用6去替換3獲得的序列必定會大於4替換3獲得的序列,所以只能選4。將4和3的位置對調後造成排列<4 6 3 2>。對調後獲得的子序列<6 3 2>仍保持減序,即這3個數可以生成的最大的一種序列。而4是第1次做爲首位的,須要右邊的子序列最小,所以4右邊的子序列應爲<2 3 6>,這樣就獲得了正確的一個序列pn+1=<4 2 3 6>。

下面概括分析該過程。假設一個有m個元素的序列pn,其下一個較大序列爲pn+1

1) 若pn最右端的2個元素構成一個增序子序列,那麼直接反轉這2個元素使該子序列成爲減序,便可獲得pn+1

2) 若pn最右端一共有連續的s個元素構成一個減序子序列,令i = m - s,則有pn(i) < pn(i+1),其中pn(i)表示排列pn的第i個元素。例如pn=<1 2 5 4 3>,那麼pn的右端最多有3個元素構成一個減序子集<5 4 3>,i=5-3=2,則有pn(i)=2 < 5=pn(i+1)。所以若將pn(i)和其右邊的子集s {pn(i+1), pn(i+2), ..., pn(m)}中任意一個元素調換必能獲得一個較大的序列(不必定是下一個)。要保證是下一個較大的序列,必須保持pn(i)左邊的元素不動,並在子集s {pn(i+1), pn(i+2), ..., pn(m)}中找出全部比pn(i)大的元素中最小的一個pn(j),即不存在pn(k) ∈ s且pn(i) < pn(k) < pn(j),而後將兩者調換位置。如今只要使新子集{pn(i+1), pn(i+2), ..., pn(i), ...,pn(m)}成爲最小序列即獲得pn+1。注意到新子集仍保持減序,那麼此時直接將其反轉便可獲得pn+1 {pn(1), pn(2), ..., pn(j), pn(m), pn(m-1), ..., pn(i), ..., pn(i+2), pn(i+1)}。

 

複雜度

最好的狀況爲pn的最右邊的2個元素構成一個最小的增序子集,交換次數爲1,複雜度爲O(1),最差的狀況爲1個元素最小,而右面的全部元素構成減序子集,這樣須要先將第1個元素換到最右,而後反轉右面的全部元素。交換次數爲1+(n-1)/2,複雜度爲O(n)。由於各類排列等可能出現,因此平均複雜度即爲O(n)。

 

擴展

1. 可否直接算出集合{1, 2, ..., m}的第n個排列?

設某個集合{a1, a2, ..., am}(a1<a2<...<am)構成的某種序列pn,基於以上分析易證得:若as<at,那麼將as做爲第1個元素的全部序列必定都小於at做爲第1個元素的任意序列。同理可證得:第1個元素肯定後,剩下的元素中若as'<at',那麼將as'做爲第2個元素的全部序列必定都小於做爲第2個元素的任意序列。例如4個數的集合{2, 3, 4, 6}構成的序列中,以3做爲第1個元素的序列必定小於以4或6做爲第1個元素的序列;3做爲第1個元素的前題下,2做爲第2個元素的序列必定小於以4或6做爲第2個元素的序列。

推廣可知,在肯定前i(i<n)個元素後,在剩下的m-i=s個元素的集合{aq1, aq2, ..., aq3}(aq1<aq2<...<aqm)中,以aqj做爲第i+1個元素的序列必定小於以aqj+1做爲第i+1個元素的序列。由此可知:在肯定前i個元素後,一共可生成s!種連續大小的序列。

根據以上分析,對於給定的n(必有n<=m!)能夠從第1位開始向右逐位地肯定每一位元素。在第1位不變的前題下,後面m-1位一共能夠生成(m-1)!中連續大小的序列。若n>(m-1)!,則第1位不會是a1,n中能夠容納x個(m-1)!即表明第1位是ax。在肯定第1位後,將第1位從原集合中刪除,獲得新的集合{aq1, aq2, ..., aq3}(aq1<aq2<...<aqm),而後令n1=n-x(m-1)!,求這m-1個數中生成的第n1個序列的第1位。

舉例說明:如7個數的集合爲{1, 2, 3, 4, 5, 6, 7},要求出第n=1654個排列。

(1654 / 6!)取整得2,肯定第1位爲3,剩下的6個數{1, 2, 4, 5, 6, 7},求第1654 % 6!=214個序列;

(214 / 5!)取整得1,肯定第2位爲2,剩下5個數{1, 4, 5, 6, 7},求第214 % 5!=94個序列;

(94 / 4!)取整得3,肯定第3位爲6,剩下4個數{1, 4, 5, 7},求第94 % 4!=22個序列;

(22 / 3!)取整得3,肯定第4位爲7,剩下3個數{1, 4, 5},求第22 % 3!=4個序列;

(4 / 2!)得2,肯定第5爲5,剩下2個數{1, 4};因爲4 % 2!=0,故第6位和第7位爲增序<1 4>;

所以全部排列爲:3267514。

 

2. 給定一種排列,如何算出這是第幾個排列呢?

和前一個問題的推導過程相反。例如3267451:

後6位的全排列爲6!,3爲{1, 2, 3 ,4 , 5, 6, 7}中第2個元素(從0開始計數),故2*720=1440;

後5位的全排列爲5!,2爲{1, 2, 4, 5, 6, 7}中第1個元素,故1*5!=120;

後4位的全排列爲4!,6爲{1, 4, 5, 6, 7}中第3個元素,故3*4!=72;

後3位的全排列爲3!,7爲{1, 4, 5, 7}中第3個元素,故3*3!=18;

後2位的全排列爲2!,5位{1, 4, 5}中第2個元素,故2*2!=4;

最後2位爲增序,所以計數0,求和得:1440+120+72+18+4=1654ong

使用以上方法而不是用遞歸的實現的好處是,能夠避免重複,好比vector是<1,1,2,3>的狀況下。另外還能夠用這個方法求組合,好比要求4個元素裏2個元素的組合,能夠求數組<0,0,1,1>的全排列,而後取出對應原數組的下標便可。

代碼:

 1     void nextPermutation(vector<int> &num) {
 2         // IMPORTANT: Please reset any member data you declared, as
 3         // the same Solution instance will be reused for each test case.
 4         int n = num.size();
 5         if(n <= 1)
 6             return;
 7         int i;
 8         int t=-1,tmp;
 9         for(i = n-1; i >= 1; i--){
10             if(num[i] > num[i-1]){
11                 t = i-1;
12                 break;
13             }
14         }
15         if(t != -1){
16             for(i = n-1; i >= t+1; i--){
17                 if(num[i] > num[t]){
18                     tmp = num[i];
19                     num[i] = num[t];
20                     num[t] = tmp;
21                     break;
22                 }
23             }
24         }
25         for(i = t+1; i <= (t+n)/2; i++){
26             tmp = num[i];
27             num[i] = num[t+n-i];
28             num[t+n-i] = tmp;
29         }
30         return;
31     }
相關文章
相關標籤/搜索