(一)题目描述
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列(即,组合出下一个更大的整数)。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。必须 原地 修改,只允许使用额外常数空间。
示例:
输入:nums = [1,2,3]
输出:[1,3,2]
(二)解题思路
1、思路重点:如何保证下一排列是大于当前排列的最小排列?
(1)动的元素越靠后越好,所以需从后向前查找
(2)前面被动的元素增幅越小越好
2、思路如下:
(1)从后向前查找第一个相邻的升序对,下标分别为i(前)、j(后)(此时i、j后面的元素均为降序,降序中的任何一个元素被后面的元素换,该排列都不会变大,所此时较小的元素为最适合被动的元素,因其靠很靠后)
(2)在[j,end]区间从后向前查找刚好大于nums[i]的第一个元素,然后与nums[i]交换;
(3)将[j,end]的元素进行升序排列(保证该排列增幅最小)
(4)若[0,end]的元素全为降序,则全部升序排列
3、借鉴力扣大佬的流程可视化,方便理解:以下解析网址为:力扣
以求 12385764
的下一个排列为例:
首先从后向前查找第一个相邻升序的元素对 (i,j)
。这里 i=4
,j=5
,对应的值为 5
,7
:
然后在 [j,end)
从后向前查找第一个大于 A[i]
的值 A[k]
。这里 A[i]
是 5
,故 A[k]
是 6
:
将 A[i]
与 A[k]
交换。这里交换 5
、6
:
这时 [j,end)
必然是降序,逆置 [j,end)
,使其升序。这里逆置 [7,5,4]
:
因此,12385764
的下一个排列就是 12386457
。
最后再可视化地对比一下这两个相邻的排列(橙色是蓝色的下一个排列):
(四)代码如下:
class Solution15 {
public void nextPermutation(int[] nums) {
// (1)从右到左记录降序;
// 原则:(1)交换的两数尽量靠右,增加的大小尽量小,才能使增加的幅度尽量小
// 步骤:(1)从右到左找第一个降序对
// (2)该降序对后面的所有数必为从右到左的升序
int smallIdx=0, largeIdx=0;
int temp=0;
int flag=0;
for (int i = nums.length - 1; i >= 0; i--) {
if (i > 0 && nums[i] > nums[i - 1]) {
flag=1;
smallIdx=i-1;
break;
}
}
if(flag==0){
Arrays.sort(nums);
return;
}
for (int i = nums.length-1; i >=smallIdx+1 ; i--) {
if(nums[i]>nums[smallIdx]){
temp=nums[i];
nums[i]=nums[smallIdx];
nums[smallIdx]=temp;
break;
}
}
Arrays.sort(nums,smallIdx+1,nums.length);
}
}
二刷错误解答:
class Solution {
public void nextPermutation(int[] nums) {
// (1)从右到左记录降序;
int temp = 0;
int flag = 0;
for (int i = nums.length - 1; i >= 0; i--) {
if (i>0&&nums[i] > nums[i - 1]) {
temp = nums[i];
nums[i] = nums[i - 1];
nums[i - 1] = temp;
flag=1;
break;
}
}
if(flag==0){
Arrays.sort(nums);
}
}
}
!!错误原因:只考虑到从后往前查找第一个处于降序的序列,但找到后的处理方法只是简单的将该降序对的两个值进行交换(即(i,j)位置的值),没考虑到[j,end]这个区间存在比i处的值小的值,
将[j,end]这个区间小且最接近num[i]的值与Num[i]交换,这样得到的值总体才会最小。