整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
arr = [1,2,3]
,以下这些都可以视作 arr
的排列:[1,2,3]
、[1,3,2]
、[3,1,2]
、[2,3,1]
。整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
例如,arr = [1,2,3]
的下一个排列是 [1,3,2]
。
类似地,arr = [2,3,1]
的下一个排列是 [3,1,2]
。
而 arr = [3,2,1]
的下一个排列是 [1,2,3]
,因为 [3,2,1]
不存在一个字典序更大的排列。
给你一个整数数组 nums
,找出 nums
的下一个排列。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:
输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:
输入:nums = [1,1,5]
输出:[1,5,1]
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 100
题目说了原地 修改,只允许使用额外常数空间。那就不能使用额外空间按大小存储数字,只能使用常数空间的变量。
我们需要做的是,找到一种方法,使新排列字典序大于旧排列,但是这个排列变大的幅度要是最小的。具体怎么做呢?我下面简单说一下。
以排列 [ 3 , 6 , 2 [3,6,\color {red} 2 [3,6,2, 3 , 3 3,\color {red}3 3,3, 1 ] 1] 1] 为例:
我描述一下该方法:
public void nextPermutation(int[] nums) {
if (nums == null || nums.length < 2) {
return;
}
// 记录反转数组的起始位置
int index = -1;
for (int i = nums.length - 2; i >= 0; i--) {
// 当左边的数比右边的数小时
if (nums[i] < nums[i + 1]) {
// 交换的数是最后两位
if (i == nums.length - 2) {
swap(i, i + 1, nums);
return;
} else {
// 比nums[i]大且最接近nums[i]的数的下标
int minIndex = i + 1;
// nums[i + 1] ~ nums[nums.length - 1]是非严格从大到小排列
for (int j = i + 2; j < nums.length; j++) {
// 从 nums[i + 1] ~ nums[nums.length - 1]中寻找比nums[i]大且最接近nums[i]的数
if (nums[i] < nums[j]) {
minIndex = j;
} else {
break;
}
}
swap(i, minIndex, nums);
// 记录反转数组的起始位置
index = i + 1;
break;
}
}
}
int l;
int r = nums.length - 1;
if (index != -1) {
l = index;
} else {
l = 0;
}
//从数组下标 l ~ r 进行数组交换
while (l < r) {
swap(l, r, nums);
l++;
r--;
}
}
// 数组下标i和j交换函数
public void swap(int i, int j, int[] nums) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
时间复杂度: O ( N ) O(N) O(N),设数组长度为 N N N,需要扫描两次数组。
空间复杂度: O ( 1 ) O(1) O(1),只需要存储常数级别的指针和变量。