LeetCode 31:下一个排列(Next Permutation)——从“为什么”彻底搞懂这道题

129 阅读3分钟

这道题乍一看很抽象,但它其实是一个贪心 + 数组操作的经典问题。
真正的难点不在代码,而在于理解:什么才叫“下一个”排列?

本文将从直觉、数学含义到代码实现,一步步拆解这道题。


一、什么是「下一个排列」?

题目要求:

给定一个整数数组 nums,将其重新排列成字典序中下一个更大的排列

关键点有两个:

  1. 必须比当前排列大
  2. 必须是所有更大排列中“最小的那个”

如果当前排列已经是最大的(完全降序),则返回最小排列(完全升序)。

举几个例子:

[1,2,3][1,3,2]
[1,3,2][2,1,3]
[3,2,1][1,2,3]

所以问题本质是:

如何让数组“刚好”变大一点点,而不是跳得太远?


二、核心思想:尽量少改动高位

在排列中:

  • 越靠左的数,权重越大
  • 越靠右的数,权重越小

想要“刚好变大”,策略一定是:

优先在右侧动手,能不改左边就不改左边

这也是整个算法“从右往左”的根本原因。


三、算法三步走(整体思路)

第一步:从右往左,找到第一个可以“变大”的位置

我们从右向左寻找第一个满足:

nums[i] < nums[i + 1]

代码:

int i = n - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
    i--;
}

这一步的含义是:

  • 跳过所有已经是“降序”的后缀
  • 找到最右边那个还有上升空间的位置

如果没找到(i < 0),说明整个数组是降序的,已经是最大排列。


第二步:在右侧找到一个“刚好比它大的数”并交换

如果找到了这个 i:

[ …… nums[i] | 右侧递减序列 ]

右侧一定是降序的。

我们要做的是:

在右侧找到一个 比 nums[i] 大,但尽量小 的数

因为要“刚好变大”。

代码:

int j = n - 1;
while (nums[j] <= nums[i]) {
    j--;
}
swap(nums, i, j);

为什么从右往左找?

  • 右侧是降序
  • 从最右边开始,第一个满足条件的数就是“最小的更大值”

第三步:反转 i 之后的部分

交换之后,右侧依然是降序的,但现在我们要让整体排列尽可能小

做法很简单:

把 i 右边的部分变成升序

代码:

reverse(nums, i + 1, n - 1);

因为之前是降序,直接反转即可。


四、完整代码实现(Java)

class Solution {
    public void nextPermutation(int[] nums) {
        int n = nums.length;
        int i = n - 2;

        // 1. 从右往左,找到第一个 nums[i] < nums[i + 1]
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }

        // 2. 如果找到了这样的 i,在右侧找一个刚好比它大的数
        if (i >= 0) {
            int j = n - 1;
            while (nums[j] <= nums[i]) {
                j--;
            }
            swap(nums, i, j);
        }

        // 3. 反转 i 之后的部分
        reverse(nums, i + 1, n - 1);
    }

    private void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

    private void reverse(int[] nums, int left, int right) {
        while (left < right) {
            swap(nums, left++, right--);
        }
    }
}

五、为什么这个算法一定正确?

总结一下逻辑闭环:

  1. 从右往左找第一个可上升点,保证高位尽量不动
  2. 在右侧选“最小的更大数”,保证增量最小
  3. 右侧重排为最小序列,保证整体最小

这正好符合「下一个排列」的定义。


六、复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
  • 完全原地修改数组,符合题目要求

七、总结一句话版本

下一个排列的本质是:
在最右侧找到一个可以“微调”的位置,让整体刚好变大,然后把后缀压到最小。