Description:
Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.
If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).
The replacement must be in-place, do not allocate extra memory.
Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.
1,2,3
→1,3,2
3,2,1
→1,2,3
1,1,5
→1,5,1
Solutions:
Approach 1: Optimized solution with time complexity of O(n)
我们首先必须明白“next permutation”的含义是什么。题目中的例子给了我们提示信息,例如对于数组[1, 2, 3]
来说,它的一个全排列是
1, 2, 3
1, 3, 2
2, 1, 3
2, 3, 1
3, 1, 2
3, 2, 1
故对于排列2, 1, 3
, 它的下一个排列便是2, 3, 1
,再下一个排列便是3, 1, 2
。如果当前排列已经是全排列的最后一种如3, 2, 1
,则下一个排列应该是正序排列, 即1, 2, 3
。
但是对于有相同数字的数组,例如[1, 1, 1, 1, 5]
来说,它的全排列为
1, 1, 1, 1, 5
1, 1, 1, 5, 1
1, 1, 5, 1, 1
1, 5, 1, 1, 1
5, 1, 1, 1, 1
通过观察上述排列,我们发现,如果把其中的逗号去除,把它们看成数字,则第n
个排列组成的数比第n-1
的要大,比n+1
的要小。
因此,求下一个排列,即求由比这个数大的下一个排列。
有了这个思路,我们的算法可如下设计:
- 从左向右遍历数组,直到找到正序的两个数
nums[i-1], nums[i]
使得num[i-1] < nums[i]
,此时我们可保证nums[i...n-1]
是逆序的。 - 在
nums[i...n-1]
中,从左向右遍历数组,找到比num[i-1]
大的第一个数nums[j]
使得nums[j] > nums[i]
,该数即是nums[i...n-1]
中比nums[i-1]
大的最小的数。 - 由于我们要找到比当前排列大的下一个组合,且
nums[i...n-1]
是逆序的。故nums[i...n-1]
该子序列已经是最大值,要让整个组合更大,需更换nums[i-1]
。故交换nums[i-1]
和nums[j]
。 - 逆序排列
nums[i...n-1]
,使得该子序列最小,这保证了新的排列是所以比题目给定排列大的排列的最小排列。
但是我们需要注意一下几点:
- 第1步中的条件必须是
num[i-1] < nums[i]
而不能相等,因为如果两数相等,可能会导致求得的下一个排列不变。例如1, 1, 1, 5, 5
的下一个排列本应该是1, 1, 5, 1, 5
,但如果有相等的条件,会导致nums[i-1] = nums[i] = 5
。而在nums[i]
右已没有更多的数,则求得的新排列不变。 - 第2步中的条件必须是
nums[j] > nums[i-1]
而不能相等。因为我们最终要交换nums[j]
和nums[i-1]
,若二者相等,则交换后无法得到更大的排列。
代码如下:
class Solution {
public void nextPermutation(int[] nums) {
int len = nums.length;
int i = len-1;
for (; i > 0; i--) {
if (nums[i] >= nums[i-1]) break;
}
if (i == 0) {
reverse(nums, 0, len-1);
return;
}
int j = len-1;
for (; j >= i; j--) {
if (nums[j] > nums[i-1]) break;
}
swap(nums, i-1, j);
reverse(nums, i, len-1);
}
public void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
public void reverse(int[] nums, int start, int end) {
for(int i = start; i <= (start+end)/2; i++) {
swap(nums, i, start+end-i);
}
}
}
- 时间复杂度分析:
最开始寻找nums[i]
和nums[i-1]
的复杂度是O(n),寻找nums[j]
的复杂度是O(n),swap()
函数的复杂度是O(1),最后reverse()
函数的复杂度是O(n)。故最终的时间复杂度是O(n)。