题目

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。

  • 例如,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 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

思路

这道题目其实是 Hot100 系列题目比较有独特思想的,主要有两个重点。

下一个排列的实现可以分为以下几个步骤:

  1. 从右向左找到第一个位置 i,使得 nums[i] < nums[i+1]。这个位置称为 pivot(枢纽)。注意:我们从倒数第二个开始向左找。
  2. 如果找到了这样的 ii >= 0),说明存在下一个排列;否则数组为完全降序,已经是最大排列。
  3. 从右向左找到第一个 j,使得 nums[j] > nums[i]。因为 i 右侧是降序的,所以从右向左第一个大于 nums[i] 的元素实际上是比 nums[i] 大的最小元素,交换后能得到刚好比原排列大的排列。
  4. 交换 nums[i]nums[j]
  5. 由于 i 右侧原先是降序(或 i < 0 时是整个数组),交换后仍是降序。我们要把它变为升序(最小顺序),只需反转 i+1 到末尾的子数组。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Solution {
public void nextPermutation(int[] nums) {
// 目标:将数组就地修改为字典序的下一个排列
// 下面的实现使用经典四步法,并附带详细中文注释,便于理解。
if (nums == null || nums.length <= 1) return;

// 1) 从右向左找到第一个位置 i,使得 nums[i] < nums[i+1]
// 这个位置称为 pivot(枢纽)。注意:我们从倒数第二个开始向左找。
int i = nums.length - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
// 这里用 >= 保证我们跳过后缀中非递增的部分(即降序部分)
i--;
}

// 如果找到了这样的 i(i >= 0),说明存在下一个排列;否则数组为完全降序,已经是最大排列
if (i >= 0) {
// 2) 从右向左找到第一个 j,使得 nums[j] > nums[i]
// 因为 i 右侧是降序的,所以从右向左第一个大于 nums[i] 的元素
// 实际上是比 nums[i] 大的最小元素,交换后能得到刚好比原排列大的排列
int j = nums.length - 1;
while (j > i && nums[j] <= nums[i]) {
j--;
}
// 3) 交换 nums[i] 与 nums[j]
swap(nums, i, j);
}

// 4) 由于 i 右侧原先是降序(或 i < 0 时是整个数组),交换后仍是降序
// 我们要把它变为升序(最小顺序),只需反转 i+1 到末尾的子数组
reverse(nums, i + 1, nums.length - 1);
}

// 交换辅助方法
private void swap(int[] nums, int a, int b) {
int t = nums[a];
nums[a] = nums[b];
nums[b] = t;
}

// 反转区间 [l, r]
private void reverse(int[] nums, int l, int r) {
while (l < r) {
swap(nums, l, r);
l++;
r--;
}
}
}