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     }
相关文章
相关标签/搜索