leetcode 下一个排列(C++,STL源码解析)
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
题解:
1.找到最大索引k,以使a [k] <a [k + 1]。如果不存在这样的索引,则该排列为最后的排列,直接翻转。
2.找出最大索引l,使a [k] <a [l]。由于k +1是这样的索引,因此l定义明确,并且满足k <l。
3.将a [k]与a [l]交换。
4.反转从a [k +1]到最后一个元素a [n]的序列。
我可以解释为:
①我们首先从后往前寻找a [k] <a [k + 1]的位置,因为我们假设索引 t > k, 那么对于任意t,肯定存在 a[t] > a[t + 1] ,也就是在k后面的所以元素已经构成了最大字典序,我们无法再增大最大字典序了,只能对所以k位置的元素进行操作。
②现在我们找到了这样一个位置后,我们要再找到一个位置与索引k进行交换才能生成下一个比原有排列字典序大且大的尽可能小的的排列假设我们从后往前找到了这样一个索引l,使得a[l] > a[k],
③交换两者之后,因为存在a[l - 1] > a[l] > a[k] > a[l + 1],所以索引k之后的仍然是最大字典序,因为我们要的是大的尽可能小的字典序,所以我们把索引k位置后的元素reverse一下变得到了最小的字典序。
eg: 2, 4, 3, 1
很明显,2后面的元素已经构成了最大的字典序,仅对后面的三个元素进行操作时没有用的了,因此按照前面说的第一步我们确定k = 0,a[k] = 2
现在我们从后往前找到大于a[k](也就是2)的第一个元素,明显 l = 2, a[l] = 3
现在我们进行交换,3, 4, 2, 1 明显索引0后的所以元素仍然构成最大字典序,按照题目要求我们reverse 索引k后面的所有元素 -> 得到 3, 1, 2, 4 就得到了我们要的结果
用我贫瘠的语言总结一下:我们从后往前寻找一个字典序已经最大的片段(也就是降序),因为他字典序已经最大我们无法通过对这个片段进行操作获得更大的字典序,因此我们对这个片段前一个索引位置进行交换操作(交换一个比它大且大的最小的元素),由于交换不改变那个片段仍然是最大字典序的事实,因此我们只需要在交换后reverse后面这个片段便可以得到我们想要的结果
参考代码:
1 class Solution { 2 public: 3 void iter_swap(vector<int>::iterator a, vector<int>::iterator b) 4 { 5 int t = *a; *a = *b; *b = t; 6 } 7 void Next_permutation(vector<int>::iterator a, vector<int>::iterator b) 8 { 9 if(a==b) return ; 10 vector<int>::iterator i=a;++i; 11 if(i==b) return ; 12 i=b;--i; 13 while(true) 14 { 15 vector<int>::iterator j=i;--i; 16 if(*i<*(i+1)) 17 { 18 vector<int>::iterator k=b; 19 while(!(*i<*--k)) ; 20 iter_swap(i,k); 21 reverse(j,b); 22 break; 23 } 24 if(i==a) 25 { 26 reverse(a,b); 27 break; 28 } 29 } 30 return ; 31 } 32 void nextPermutation(vector<int>& nums) 33 { 34 Next_permutation(nums.begin(), nums.end()); 35 } 36 };