题目描述
题目链接
https://leetcode.cn/problems/next-permutation/
思路
「下一个排列」的定义是:给定数字序列的字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列,即升序排列。我们可以将该问题形式化地描述为:给定若干个数字,将其组合为一个整数。如何将这些数字重新排列,以得到下一个更大的整数。如的下一个更大的数为
。如果没有更大的整数,则输出最小的整数。
以为例,其排列依次为:
:::danger
:::
可以看到有这样的关系:
。
1. 算法推导
那如何得到这样的排列顺序?我们可以这样来分析:
我们希望下一个数比当前的数大,这样才满足「下一个排列」的定义。因此只需要将后面的「大数」与前面的「小数」交换,就能得到一个更大的数。比如,将
和
交换就能得到一个更大的数
。我们还希望下一个数增加的幅度尽可能的小,这样才满足「下一个排列与当前排列紧邻」的要求。为了满足这个要求,我们需要:
- 在尽可能靠右的低位进行交换,因此需要从后向前查找。
- 将一个尽可能小的「大数」与前面的「小数」交换。比如
,下一个排列应该把
和
交换而不是把
和
交换。
- 将「大数」换到前面后,需要将「大数」后的所有数重置为升序,升序排列就是最小的排列。以
为例:首先按照上一步,交换
和
,得到
;之后还需要将
之后的数字重置为升序,得到
。显然
比
更小,所以
就是
的下一个排列。
2. 算法过程
标准的「下一个排列」算法可以描述为:
- 从后向前查找第一个相邻升序的元素对
,满足
,此时
必然是降序。
- 在
从后向前查找第一个满足
的
。
、
分别就是上文所说的「小数」和「大数」。
- 将
与
交换。
- 可以断定这时
必然是降序,此时逆置
使其升序。
- 如果在步骤一中找不到符合的相邻元素对,说明当前
为一个降序顺序,则直接跳到步骤四。
下面,我们举个例子来说明一下,以求的下一个排列为例:

首先从后向前查找第一个相邻升序的元素对。这里即
,对应的值为
:

然后在从后向前查找第一个大于
的值
。这里
是
,故
是
:

将与
交换。这里交换
、
:

这时必然是降序,逆置
使其升序。这里逆置
:

因此,的下一个排列就是
。最后我们再对比一下这两个相邻的排列(橙色是蓝色的下一个排列):
代码实现
public void nextPermutation(int[] nums) {if (nums == null || nums.length == 0) {return;}// 从后向前查找第一个相邻升序的元素对(i,j)int i = -1, j = -1;for (int index = nums.length - 1; index > 0; index--) {if (nums[index] > nums[index - 1]) {j = index;i = index - 1;break;}}// 如果存在逆序对,在[j,end)从后向前查找第一个大于nums[i]的值nums[k],然后交换nums[i]与nums[k]if (j != -1) {int k = j; // 注意,k默认为j,因为至少j是比i大的for (int n = nums.length - 1; n >= j + 1; n--) {if (nums[n] > nums[i]) {k = n;break;}}swap(nums, i, k);} else {// 如果不存在逆序对,则整体都是降序的,此时需要把整个数组做一个逆置j = 0;}// 这时 [j,end) 必然是降序,逆置 [j,end) 使其升序int middle = (nums.length - j) / 2;for (int k = 0; k < middle; k++) {swap(nums, j + k, nums.length - 1 - k);}}private void swap(int[] nums, int i, int j) {int tmp = nums[i];nums[i] = nums[j];nums[j] = tmp;}
复杂度分析
- 时间复杂度:
,其中
为给定序列的长度。我们至多只需要扫描两次序列,以及进行一次序列的反转操作。
- 空间复杂度:
,只需要常数的空间存放若干变量。
