【力扣100题】89.下一个排列

一、题目描述

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

例如,arr = [1,2,3] 的排列有:[1,2,3][1,3,2][3,1,2][2,3,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]

二、解题思路总览

方法 时间复杂度 空间复杂度 说明
字典序算法 O(n) O(1) 找破坏点 + 替换 + 翻转
暴力枚举 O(n! * n) O(n!) 生成所有排列找下一个

字典序算法是标准解法,一次遍历完成。


三、方法一:字典序算法(推荐)

3.1 核心思想

从一个排列生成下一个更大排列的步骤:

  1. 找破坏点 :从后往前找第一个升序对 nums[i] < nums[i+1]i 是破坏点
  2. 找替换点 :在 [i+1, n) 区间从后往前找第一个大于 nums[i] 的元素
  3. 交换 :交换 nums[i]nums[j]
  4. 翻转[i+1, n) 区间变成升序(原来降序,反转即可)

3.2 算法流程图

复制代码
示例1: nums = [1, 2, 3] -> [1, 3, 2]

+------------------------------------------------------------+
|  Step 1: 找破坏点 i(从后往前找第一个升序对)                 |
+------------------------------------------------------------+

从后往前遍历:
  i=1: nums[1]=2 < nums[2]=3 -> 找到!i=1
  [1, 2, 3]
       ↑ i

+------------------------------------------------------------+
|  Step 2: 找替换点 j(从后往前找第一个大于 nums[i] 的元素)    |
+------------------------------------------------------------+

从后往前遍历:
  j=2: nums[2]=3 > nums[1]=2 -> 找到!j=2
  [1, 2, 3]
         ↑ j

+------------------------------------------------------------+
|  Step 3: 交换 nums[i] 和 nums[j]                            |
+------------------------------------------------------------+

交换: nums[1]=2 <-> nums[2]=3
  [1, 3, 2]

+------------------------------------------------------------+
|  Step 4: 翻转 [i+1, n) 区间                                  |
+------------------------------------------------------------+

[i+1=2, n=3) 区间只有 [2],反转后仍是 [2]

最终结果: [1, 3, 2]

示例2: nums = [2, 3, 1] -> [3, 1, 2]

+------------------------------------------------------------+
|  Step 1: 找破坏点 i                                          |
+------------------------------------------------------------+

从后往前遍历:
  i=1: nums[1]=3 > nums[2]=1 -> 下降,不满足
  i=0: nums[0]=2 < nums[1]=3 -> 找到!i=0
  [2, 3, 1]
   ↑ i

+------------------------------------------------------------+
|  Step 2: 找替换点 j                                          |
+------------------------------------------------------------+

从后往前遍历:
  j=2: nums[2]=1 <= nums[0]=2 -> 不满足
  j=1: nums[1]=3 > nums[0]=2 -> 找到!j=1
  [2, 3, 1]
       ↑ j

+------------------------------------------------------------+
|  Step 3: 交换 nums[i] 和 nums[j]                            |
+------------------------------------------------------------+

交换: nums[0]=2 <-> nums[1]=3
  [3, 2, 1]

+------------------------------------------------------------+
|  Step 4: 翻转 [i+1, n) 区间                                  |
+------------------------------------------------------------+

[i+1=1, n=3) 区间是 [2, 1]
反转后: [1, 2]

最终结果: [3, 1, 2]

示例3: nums = [3, 2, 1] -> [1, 2, 3]

+------------------------------------------------------------+
|  Step 1: 找破坏点 i                                          |
+------------------------------------------------------------+

从后往前遍历:
  i=1: nums[1]=2 > nums[2]=1 -> 下降,不满足
  i=0: nums[0]=3 > nums[1]=2 -> 下降,不满足
  没有找到破坏点!

+------------------------------------------------------------+
|  Step 2: 整个数组是降序,没有更大的排列                       |
+------------------------------------------------------------+

执行 Step 4: 反转整个数组
  [3, 2, 1] -> [1, 2, 3]

最终结果: [1, 2, 3]

3.3 完整代码

cpp 复制代码
class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int n = nums.size();

        // 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 (nums[j] <= nums[i]) {
                j--;
            }
            swap(nums[i], nums[j]);
        }

        // Step 3: 反转 [i+1, n) 区间
        reverse(nums.begin() + i + 1, nums.end());
    }
};

3.4 代码逐行解析

cpp 复制代码
int i = n - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) {
    i--;
}
  • 从后往前遍历,找第一个 nums[i] < nums[i+1] 的位置
  • nums[i] >= nums[i+1] 意味着是降序,继续往前找
  • 如果找不到(i < 0),说明整个数组是降序,没有更大的排列
cpp 复制代码
if (i >= 0) {
    int j = n - 1;
    while (nums[j] <= nums[i]) {
        j--;
    }
    swap(nums[i], nums[j]);
}
  • [i+1, n) 区间从后往前找第一个大于 nums[i] 的元素
  • 为什么要从后往前?因为要找最小的比 nums[i] 大的元素
  • 交换这两个元素
cpp 复制代码
reverse(nums.begin() + i + 1, nums.end());
  • 反转 [i+1, n) 区间
  • 这个区间原来是降序(因为之前是从后往前找的)
  • 反转后变成升序,得到最小的排列

3.5 复杂度分析

复杂度 说明
时间 O(n) 最多遍历三遍:找破坏点、找替换点、反转
空间 O(1) 只用常数个变量

四、方法二:暴力枚举(不推荐)

4.1 核心思想

生成数组的所有排列,按字典序排序,找到当前排列的下一个。

4.2 完整代码

cpp 复制代码
class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        vector<vector<int>> permutations;
        sort(nums.begin(), nums.end());
        do {
            permutations.push_back(nums);
        } while (next_permutation(nums.begin(), nums.end()));

        // 找到当前排列的下一个
        // ...(省略)
    }
};

4.3 复杂度分析

复杂度 说明
时间 O(n! * n) 生成所有排列
空间 O(n!) 存储所有排列

五、方法对比总结

方法 时间复杂度 空间复杂度 推荐指数
字典序算法 O(n) O(1) ★★★★★
暴力枚举 O(n! * n) O(n!) ★☆☆☆☆
复制代码
字典序算法的核心思想:

1. 找破坏点:从后往前找第一个升序对
   - 破坏点是全局的最高位变化点
   - 在它之后的部分是降序的

2. 找替换点:从后往前找第一个大于破坏点元素的值
   - 因为是从后往前找,所以找到的是最小的比破坏点大的值

3. 交换 + 翻转:
   - 交换后,[破坏点+1, 末尾) 仍然是降序
   - 反转后变成升序,得到最小排列

六、面试追问 FAQ

问题 回答
Q: 为什么从后往前找破坏点? 从后往前是为了找到最短的变动前缀,保持高位不变,只修改最低位
Q: 为什么从后往前找替换点? 从后往前找第一个大于 numsi 的,可以保证替换后剩余部分最小
Q: 反转区间为什么是升序? 因为 [i+1, n) 原来是降序(从后往前找破坏点时保证的),反转后变成最小升序
Q: 如果没有破坏点怎么办? 说明整个数组是降序,没有更大的排列,直接反转整个数组得到最小排列
Q: 如何求前一个排列? 反过来:找第一个降序对(numsi > numsi+1),然后交换,再反转
Q: 字典序算法的应用场景? 字符串的全排列、组合数学、next_permutation 函数实现

七、相关题目

题目 难度 关键点
31. 下一个排列 中等 字典序算法
46. 全排列 中等 回溯生成所有排列
47. 全排列 II 中等 去重全排列
60. 排列序列 中等 第 k 个排列
556. 下一个更大元素 III 中等 字符串版本的下一个排列

八、总结

要点 说明
核心思想 字典序算法:找破坏点 + 找替换点 + 交换 + 翻转
破坏点 从后往前找第一个升序对 nums[i] < nums[i+1]
替换点 从后往前找第一个大于 nums[i] 的元素
翻转 [i+1, n) 区间反转变成升序
时间复杂度 O(n)
空间复杂度 O(1)
特殊情况 没有破坏点时反转整个数组

相关推荐
MicroTech20251 小时前
量子隐形传态路线的瓶颈与突破,微算法科技(MLGO)以技术创新助力量子通信长距离组网
科技·算法·量子计算
洛水水1 小时前
【力扣100题】90.寻找重复数
算法·leetcode·职场和发展
鱼子星_1 小时前
【数据结构】排序的拓展——快速排序的生态多样性与归并排序沾染文件操作
c语言·数据结构·算法
alphaTao1 小时前
LeetCode 每日一题 2026/6/8-2026/6/14
算法·leetcode
KaMeidebaby1 小时前
卡梅德生物技术快报|噬菌体展示文库构建全流程解析 | 大豆球蛋白纳米抗体筛选实践
人工智能·python·tcp/ip·算法·机器学习
kkkAloha2 小时前
链表解题总结
数据结构·链表
CC数学建模2 小时前
2026年第十六届APMCM 亚太地区大学生数学建模竞赛(中文赛项)赛题B题:高性能芯片热管理系统的优化问题完整思路、代码、模型、文章,全网首发高质量分享!
python·算法·数学建模
爱睡懒觉的焦糖玛奇朵2 小时前
【视觉检测之人员奔跑检测算法开发思路】
人工智能·python·深度学习·算法·yolo·视觉检测
05候补工程师2 小时前
【408考研复习】数据结构核心笔记:字符串模式匹配与内部排序算法全解析
数据结构·经验分享·笔记·考研·算法·排序算法