LeetCode 31. 下一个排列:从直觉到算法的完整推导

一、题目描述

给定整数数组 nums,表示一个排列。

原地 将其修改为字典序更大的下一个排列

  • 如果不存在更大的排列,则将其重排为最小排列(升序)

  • 只允许使用 **O(1)**​ 额外空间。

示例

示例 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,2,3]的全排列:

复制代码
[1,2,3]
[1,3,2]
[2,1,3]
[2,3,1]
[3,1,2]
[3,2,1]

可以发现一个规律:

  • 越靠右的数字变化越快

  • 想要得到"下一个更大的排列",就要:

    • 尽可能少地改变高位

    • 让低位变大一点点

这就引出了经典解法:从右往左扫描 + 一次交换 + 反转后缀


三、核心思路拆解(重点)

Step 1:从右向左找到第一个「下降的位置」

定义指针 i,从右往左扫描,找到第一个满足:

复制代码
nums[i] < nums[i + 1]

这个 nums[i]就是需要被增大的位置

  • 如果找不到这样的 i,说明整个数组是非递增的,已经是最大排列

  • 此时直接反转整个数组即可


Step 2:从右向左找到第一个比 numsi 大的数

再从右往左扫描,找到第一个:

复制代码
nums[j] > nums[i]

这个 nums[j]比当前位稍大的最小数

交换 nums[i]nums[j]


Step 3:反转 i+1 到末尾的区间

交换后,i+1到末尾仍然是降序排列的。

为了让结果尽可能小,需要将这部分反转成升序


四、举个例子(关键理解)

nums = [1,2,3,5,4,2]为例。

第一步:找下降位置

从右往左:

复制代码
2 < 4 ✅
4 < 5 ❌

找到 i = 3nums[i] = 3


第二步:找可交换的数

从右往左找第一个大于 3 的数:

复制代码
2 ❌
4 ✅

交换 34

复制代码
[1,2,4,5,3,2]

第三步:反转后缀

反转 i+1之后的区间:

复制代码
[5,3,2] → [2,3,5]

最终结果:

复制代码
[1,2,4,2,3,5]

✅ 这就是字典序的下一个排列。


五、为什么这样做一定正确?

步骤 目的
找下降位 保证高位改动尽可能小
找稍大值 保证新排列只大一点点
反转后缀 后缀从最大变为最小

这是数学上严格成立的字典序构造方式


六、Java 代码实现

复制代码
class Solution {
    public void nextPermutation(int[] nums) {
        int n = nums.length;

        // Step 1: 从右往左找第一个 nums[i] < nums[i + 1]
        int i = n - 2;
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }

        // Step 2: 如果找到了,找可交换的位置
        if (i >= 0) {
            int j = n - 1;
            while (j > i && nums[j] <= nums[i]) {
                j--;
            }
            swap(nums, i, j);
        }

        // Step 3: 反转 i+1 到末尾
        reverse(nums, i + 1, n - 1);
    }

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

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

七、复杂度分析

指标 数值
时间复杂度 **O(n)**​
空间复杂度 **O(1)**​
  • 每个元素最多被访问两次

  • 完全符合题目对原地修改的要求


八、易错点总结(面试高频)

  1. 找不到下降位时,一定要反转整个数组

  2. 交换后必须反转后缀,否则结果不是"下一个最小"

  3. 不要试图排序后缀,排序是 O(n log n),这里是 O(n)


九、总结

解法 特点
暴力枚举 超时
排序 不满足原地要求
本题解法 ✅ O(n),O(1),面试标准答案

**一句话记忆口诀:**​

从右找降位,换个大一点,后缀全反转。

这道题是面试中非常经典的数组 + 贪心 + 字典序综合题,强烈建议熟练掌握。