题目
难度中等528实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
思路
三个变量 i j k,三步
升序对(i,j),找到第一个大于i的k交换,逆序j到结束。
字典排序的原理:
- 下一个排列意味着,下一个数比当前的数更大,因此要将后面的大数与前面的小数交换,比如123456更大的数字是123465.
- 希望下一个数增加的幅度尽可能的小。因此在尽可能靠右的低位进行交换,越靠前交换最后整个数字越大,因此从后向前查找。
- 将一个尽可能小的大数与前面的小数进行交换。比如123465应该交换54而不是64.因此从后向前遍历,找到第一个升序对(i,j),也就是前面需要交换的小数。
- 从后向前找到一个大于小数的大数k,进行交换i和k。
- j之后的数字一定为降序,逆置j之后的数字,使其变为升序。
- 如果在第三步找不到升序对,说明原始序列是一个降序对,那么直接逆序整个数组。
图解过程
(即:找到整个图的第一个凹陷处,用尽可能小的数字去替换掉凹陷处的数字。然后将后面逆序全部改为升序,更小。)
代码
public void nextPermutation(int[] nums) {
if(nums==null){
return;
}
//从后向前找到升序对,如果没有升序对,那么说明整个是降序的,就将整个数组逆序
int i = 0;
int j = nums.length-1;
boolean flag = false;
for( j = nums.length-1;j>0;j--){
if(nums[j-1]<nums[j]){
i = j-1;
flag = true;
break;
}
}
int k = nums.length-1;
//从后向前找第一个第一个满足大于i位置上的数字
if(flag){
while(k!=0){
if(nums[k]>nums[i]){
swap(nums,i,k);
//j到结尾由降序改为升序
reverse(nums,j,nums.length-1);
break;
}
k--;
}
}
else{
reverse(nums,0,nums.length-1);
}
}
public void swap(int []nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public void reverse(int []nums,int i,int j){
while(i<j){
swap(nums,i,j);
i++;
j--;
}
}