实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。 1,2,3 → 1,3,2
3,2,1 → 1,2,3 1,1,5 → 1,5,1
思路分析
①是否存在更大的排列
更大的排列
考虑最大的字典序排列: 降序排列 [1,2,3] →
[3,2,1] 对于 [3,2,1]
序列,已经是最大字典序了
假如序列存在一个更大的排列,那它必然不是降序排列
因此判断是否存在下一个排列的条件就是
顺序遍历数组,判断是否完全降序
②找到下一个排列
首先对于[1,2,3,4,5,6],假如我们要人工找到下一个排列,我们知道要把倒数两位调换一下[1,2,3,4,6,5],这样值的变动最小,字典序大小变动也最小.
为什么最后两位调换之后字典序会变大呢?
我们可以发现一个本质: 它们是升序的,
升序排列那么必然就存在可以调换成降序的操作空间,因此可以改动升序关系的任意两位来获取一个更大的字典序
如 [1,2,5,4,3,6]
知道了获得更大序的方法,接下来我们要确定什么操作能得到最小的更大序(下一个更大的序列)
其实字典序类似于数的大小关系. 比如[1,2,3] →
[2,1,3][1.3.2]
所以为了找到最小的更大序列,我们操作的升序数对要尽可能的小.所以目标就变成了:
找到最靠后的一对升序数对
[9,8,3,6,5,4]→[9,8,4,3,5,6]
最靠后的/最小的 升序数对是
[3,4],初步调换后是[9,8,4,6,5,3]
显然这个结果还不太对.
找到了最小的升序数对只是保证了在一步操作的范围内,我们增加的值是最小的.
换句话说,其实再多加几步操作才能保证是下一个序列. 来看看我们还差些什么
[9,8,4,6,5,3] →[9,8,4,3,5,6] ,
4的后面原本是降序,被整理成了升序
首先在我们的算法里,我们从后往前找一个升序的"峰值"
换句话说,我们在找到""峰值""之前,也即从末尾到"峰值"",必然是降序的,也就是局部最大的序列.
因此我们调换了一对最小的升序对,整个序列在""峰值""之前已经是最小的了,
但在调换位置之后的局部仍然是降序,即现在的序是前半段最小+后半段最大,
因此将""峰值""往后调整成升序,即后半段最小即可构成"最小+最小"的下一个排列.
题解代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| class Solution { public: void nextPermutation(vector<int& nums) { if(nums.size()<=1) return ; int flag=0; int nextid=nums.size()-1; int currentid=nextid-1; while(currentid=0) { if(nums[nextid]nums[currentid]) { flag=1; break; } nextid--; currentid--; } if(flag==1) { while(nextid+1<nums.size()) { if(nums[nextid+1]nums[currentid] ) nextid++; else break; } tempswap(nums[currentid],nums[nextid]); nextid=nums.size()-1; while(currentid+1<nextid) { tempswap(nums[nextid],nums[currentid+1]); nextid--; currentid++; } } else { nextid=nums.size()-1; currentid=0; while(currentid<nextid) { tempswap(nums[nextid],nums[currentid]); nextid--; currentid++; } } return ; } void tempswap(int& a,int& b) { int temp=b; b=a; a=temp; } };
|