[LeetCode] next_permutation

概念

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

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

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

設當前序列爲pn,下一個較大的序列爲pn+1,那麼不存在pm,使得pn < pm < pn+1spa

 

問題

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

 

數學推導

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

咱們用<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>。blog

下面概括分析該過程。假設一個有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. 給定一種排列,如何算出這是第幾個排列呢?

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

後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=1654

 

 

 

next_Permutation Code 以下

 1 class Solution {
 2     public:
 3         void nextPermutation(vector<int> &num) {
 4             // Start typing your C/C++ solution below
 5             // DO NOT write int main() function
 6             int i;
 7             vector<int>::iterator iter = num.end();
 8             int maxNum = INT_MIN;
 9 
10             //step 1, find the first number which violate the increase form right to left, we call it AAA
11             // take 5 6 7 8 4 3 2 1 for a example, find AAA = 7;
12             for(i = num.size() - 2; i >= 0; i--)
13             {   
14                 if (num[i] < num[i+1])
15                     break;
16             }   
17 
18             if (i >= 0) //i found
19             {   
20                 int j;
21                 int minNum = INT_MAX;
22                 //step 2, find the first number which is large than AAA form right to left, we call it BBB
23                 // take 5 6 7 8 4 3 2 1 for a example, find BBB = 8;
24                 for(j = num.size() - 1; j >= 0; j--)
25                 {
26                     if (num[j] > num[i])
27                         break;
28                 }
29 
30                 //step 3, swap AAA and BBB
31                 // take 5 6 7 8 4 3 2 1 for a example, exchanged array is 5 6 8 7 4 3 2 1 
32                 int t = num[i];
33                 num[i] = num[j];
34                 num[j] = t;
35 
36                 //step 4, reverse all digit after AAA's position
37                 // take 5 6 7 8 4 3 2 1 for a example, exchanged array is 5 6 8 7 4 3 2 1, then reverse 7 4 3 2 1
38                 // the output array is 5 6 8 1 2 3 4 7
39                 int k = i + 1;
40                 j =  num.size() - 1;
41                 while(k < j)
42                 {
43                     t = num[k];
44                     num[k] = num[j];
45                     num[j] = t;
46 
47                     k++;
48                     j--;
49                 }
50             }
51             else
52                 //sort(num.begin(), num.end());            
53             {
54                 int k = 0;
55                 int j =  num.size() - 1;
56                 while(k < j)
57                 {
58                     int t = num[k];
59                     num[k] = num[j];
60                     num[j] = t;
61 
62                     k++;
63                     j--;
64                 }
65             }
66         }
67 };

 總結:

       next_permutation的實現過程以下:

         首先,從最尾端開始往前尋找兩個相鄰的元素,令第一個元素是i, 第二個元素是ii,且知足i<ii; (找破壞了遞增的那個元素AAA,位置m)

         而後,再從最尾端開始往前搜索,找出第一個大於i的元素,設其爲j;(找比AAA大的元素BBB)

         而後,將i和j對調,再將ii及其後面的全部元素反轉。(交換AAA 和BBB,而後reverse m右邊的元素)

         這樣獲得的新序列就是「下一個排列」。

 

     prev_permutation的實現過程以下:

              首先,從最尾端開始向前尋找兩個相鄰的元素,令第一個元素爲i,第二個元素爲ii,且知足i>ii(找破壞了遞減的那個元素AAA,位置m)

              而後,從最尾端開始往前尋找第一個小於i的元素,令它爲j(找比AAA小的元素BBB)

              而後,將i和j對調,再將ii及其以後的全部元素反轉。(交換AAA 和BBB,而後reverse m右邊的元素)

    這樣獲得的序列就是該排列的上一個排列。

相關文章
相關標籤/搜索