31. 下一个排列(中等)
实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列(即,组合出下一个更大的整数)。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
示例 2:
示例 3:
示例 4:
提示:
- 1 <= nums.length <= 100
- 0 <= nums[i] <= 100
思路
最重要的是弄清楚什么是下一个排列?这个排列“递增”的规律是什么?
排列组合的顺序是自小到达逐渐演变的;例如:对于5个数字的排列12354和12345,排列12345在前,排列12354在后。按照这样的规定,5个数字的所有的排列中最前面的是12345,最后面的是54321。
全排列的算法如下:
1)从排列的最右端开始,找出第一个比右边数字小的数字(即 a[i] < a[i+1]
)的序号i(i为数组下标);
2)在a[i]的右边的所有数字中(即[i + 1, n]
中),从右到左找出第一个比a[i]大的数
的序号j;(因为右边的数从右至左是递增的) ;
3)对换a[i]和a[j]
4)再将a[j + 1] … a[n]反转,这就是下一个排列
。
这题更像是一个数学题,知道排列的规律很好处理,但是…
代码
class Solution {
public void nextPermutation(int[] nums) {
int n = nums.length;
int left = n - 2;
// 从后往前找到第一个nums[i] > nums[i-1]的值,将(i-1)赋值给left
while (left >= 0 && nums[left] >= nums[left + 1]) {
left --;
}
// 可能当前排列是最大的,所以要判断left >= 0
if (left >= 0) {
int j = nums.length - 1;
// 从[left + 1, n]中找到靠右侧第一个大于nums[left]的值
while (j >= 0 && nums[left] >= nums[j]) {
j--;
}
swap(nums, left, j);
}
//将[left + 1, n]位置的数据正序排序(反转后的即为正序排序)
reverse(nums, left + 1, n - 1);
}
private void swap(int[] nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
private void reverse(int[] nums, int left, int right) {
while (left < right) {
swap(nums, left, right);
left++;
right--;
}
}
}
复杂度分析
-
时间复杂度:O(N),其中N 为数组长度。我们至多只需要扫描两次序列、进行一次反转操作。
-
空间复杂度:O(1),存放诸如n、left等变量