LeetCode-31 下一个排列
题目地址
题目解析
-
这个题目相当抽象,以下用通俗语言解释一遍:
-
对于一个序列[1,2,3],可能存在多个排列:
-
[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]
-
-
这些排列的每一个元素可以连起来看成一个整数
-
[1,2,3]可以看成123
-
[1,3,2]可以看成132
-
-
以[1,2,3]为例将其所有的排列转化为整数,并进行升序排序如下:
-
[123,132, 213, 231, 312, 321]
-
-
那么[1,3,2]是[1,2,3]的下一个排列,因为它是所有大于[1,2,3]转化的整数的排列转化的整数中最小的
-
算法解析(参考官方的题解)
-
则现在的目的是将原序列的整数调大,大家可以参考显微镜的微准焦螺旋:当需要稍微调整显微镜的焦距时,我们会使用微准焦螺旋来适当调整,因为微准焦螺旋旋转一周只会让粗准焦螺旋旋转一个很小的角度,同理低位的数要连续增加到进位才能让高位增加一点(也可以参考钟表的秒针与分针与时针的关系)。
-
因此为了保证较小的调整,越低位的调整对整体大小的影响越微弱,我们从后往前调试序列,使它大于原序列,且最接近原序列
-
-
从右向左寻找一个升序序列,这个升序列是该区间中所有排列中最大的,因此不管对这个序列怎么调整都无法找到比它大的排列。按照第一条的介绍,我们只需要微调这个升序列,就能达到微调整个序列的目的。既然该升序列无法靠这个区间中的数调大,那么就将升序列前的一个数引入进来。
-
由于从后往前的升序列无法调大,那么将其前面的一个数引进来,构成新序列,此时只能微调这个最高位让整体增大幅度较小
-
微调不能动这个序列前的数,否则按照第一条的示例,会影响整体的幅度,因此只能在后面的升序列中寻找稍大于序列最高位的数,以达到调整的目的。
-
找到以后交换最高位的数和(从后向前的)升序列对应数的位置,此时升序列的属性不发生改变,它还是从右往左的升序列(它的逆序是最小的排列)
-
-
我们调整了高位,为了让这个区间的数和原来对应区间的数相差最小(变化幅度最小),最高位是不能动的,此时把后面的升序列逆序就变成了区间最小的排列,这样整体的变化幅度最小。
-
如果不好理解的话可以参考数字的进位,由199到200时,最高位要加一,但是后面的位要变得最小即0.
-
-
-
如果升序列前面没有数了,则表示这个序列已经无法调大,按照题目的意思,要把最小的排列返回出去,因此只需要逆序即可。
代码实现
-
实现分三步
-
从后向前寻找升序列
-
引入升序列前得一个数,在升序列中寻找比它稍大的数交换
-
如果升序列前没有数,这一步不用进行
-
-
将升序列逆序
-
class Solution {
public:
void iswap(int& a,int& b){
int c = a;
a = b;
b = c;
}
void nextPermutation(vector<int>& nums) {
// 从后往前寻找升序列
int i;
int j;
for(i = nums.size()-1;i>0;--i){
if(nums[i-1]<nums[i]){
// 从后向前的升序列结束,在升序列中寻找可以交换的值
for(j = nums.size()-1;j>=i;--j){
if(nums[i-1] < nums[j]){
iswap(nums[i-1],nums[j]);
break;
}
}
break;
}
}
// 逆序从右向左的升序列[i,nums.size()-1]
j = nums.size()-1;
while(i < j){
iswap(nums[i],nums[j]);
++i;
--j;
}
}
};