【技巧】LC 31.下一个排列

文章目录

前言

本专栏文章为《LeetCode 热题 100》的刷题题解,相关内容如有侵权,立即删除。

一、题目

1、原题链接

31.下一个排列

2、题目描述

二、个人思路整理

1、思路分析

思路关键:

  1. 尽量在低位(右侧)调整:越靠右改变,增幅越小。
  2. 用"稍大一点"的较小数替换它,并将后面的数字按升序重排,使增幅最小化。

核心步骤:

  1. 从后向前寻找第一个相邻升序对 (i, i + 1),满足 nums[i] < nums[i + 1]
    • 此时,区间 [i + 1, n - 1] 必定是一个单调不增(降序)的序列,它已经达到了局部字典序的最大值,无法在内部通过重排变得更大。
    • 如果遍历到最左侧都没找到这样的 i(即整个数组降序,如 [3, 2, 1]),说明已经是全局最大,直接跳到步骤 3 进行全数组反转即可。
  2. 在降序区间 [i + 1, n - 1] 中从后向前寻找第一个满足 nums[j] > nums[i] 的元素
    • 由于该区间是降序的,从后往前找遇到的第一个大于 nums[i] 的元素 nums[j],就是该区间里刚好大于 nums[i] 的最小值。
    • 交换 nums[i] 和 nums[j]。交换后,区间 [i + 1, n - 1] 依然保持严格的单调不增性质。
  3. 反转区间 [i + 1, n - 1]
    • 为了让新生成的排列尽量小,必须让低位数字按升序排列。
    • 因为区间 [i + 1, n - 1] 本身已经是降序排列,直接使用双指针将其就地翻转,即可在 O ( n ) O(n) O(n) 时间内变更为升序。

以数组 [1, 2, 7, 4, 3, 1] 为例:

  1. 从后往前看,7 -> 4 -> 3 -> 1 是降序,第一个出现 nums[i] < nums[i+1] 的位置是 2 < 7,所以索引 i = 1(数值为 2)。
  2. 从末尾往前在 [7, 4, 3, 1] 中找第一个大于 2 的数,找到 3(索引 j = 4),交换它们,数组变为 [1, 3, 7, 4, 2, 1]。
  3. 将索引 i + 1 到末尾的部分 [7, 4, 2, 1] 进行翻转,得到 [1, 2, 4, 7]。
  4. 最终结果为 [1, 3, 1, 2, 4, 7]。

2、解题代码

cpp 复制代码
class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int n = nums.size();
        // 从倒数第二个元素开始向前扫描
        int i = n - 2;

        // 步骤1:从后向前寻找第一个破坏单调不增(降序)的元素 nums[i]
        // 使得 nums[i] < nums[i + 1]
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }

        // 步骤2:如果找到了符合条件的 i(说明当前排列不是完全降序的全局最大排列)
        if (i >= 0) {
            int j = n - 1;
            // 在区间 [i + 1, n - 1] 中从后向前寻找第一个严格大于 nums[i] 的元素
            // 由于该区间本身是降序的,从后往前找到的一个数就是刚好大于 nums[i]
            while (j >= 0 && nums[j] <= nums[i]) {
                j--;
            }
            // 交换 nums[i] 和 nums[j],交换后 [i + 1, n - 1] 依然保持降序
            swap(nums[i], nums[j]);
        }

        // 步骤3:反转 [i + 1, n - 1] 区间
        // 将原先的降序区间反转为升序区间,从而使低位增幅最小,得到下一个字典序排列
        // 若 i < 0(即原数组整体降序),则整体反转为最小升序排序
        reverse(nums.begin() + i + 1, nums.end());
    }
};

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),至多进行两次线性扫描和一次区间反转。
  • 空间复杂度: O ( 1 ) O(1) O(1),纯原地指针交换,符合常数额外空间的要求。

三、知识风暴

字典序(Lexicographical Order) 是本题的核心概念。它本质上是把排列看作字符串,按「从左到右逐位比较」的方式确定大小关系:第一位不同则第一位决定大小,第一位相同则比较第二位,依此类推。本题要求「下一个排列」,就是在字典序意义下找到比当前排列「刚好大一点」的那个排列。

算法核心思想:

  • 字典序的单调性:一个排列的字典序大小,由「第一个不同的位置」决定。越靠右的数字变化,对整体字典序的增幅越小------这正是「尽量在低位调整」这一思路的理论依据。
  • 降序区间的意义 :从后向前扫描时,一旦遇到 nums[i] < nums[i + 1],说明 [i + 1, n - 1] 区间已经是降序(即该区间在字典序上已达局部最大)。此时必须进位到 nums[i],才能得到更大的排列。
  • 与贪心的区别 :贪心每一步只做当前最优选择、不回溯;而本题的「从后向前找第一个升序对」本质上是一种有策略的贪心------它先锁定增幅最小的进位位置,再在降序区间内选择「刚好大于」的交换对象,从而保证整体增幅最小。

常见对比:字典序 vs 数值大小

  • 字典序 :按位比较,[1, 2, 10] 的字典序小于 [1, 2, 3](因为 1 < 3),但数值上 10 > 3。本题的「下一个排列」严格按字典序定义。
  • 数值大小:直接比较整体数值,与字典序在位数相同时结论一致,但本题关注的是排列的字典序关系,而非数值大小。
  • 共同点:两者都依赖「从左到右逐位比较」的规则。区别在于字典序不关心数字的数值大小,只关心每一位的相对顺序。

双指针与原地算法的设计思想:

  • 核心思想 :在降序区间 [i + 1, n - 1] 中,用双指针从两端向中间交换,即可在 O ( n ) O(n) O(n) 时间内将降序反转为升序,无需额外数组。
  • 与本题的联系 :交换 nums[i] 与 nums[j] 后,区间 [i + 1, n - 1] 依然保持降序(因为 nums[j] 是区间内刚好大于 nums[i] 的最小值),因此可以直接用双指针反转,无需重新排序。
  • 注意事项 :若整个数组本身就是降序(如 [3, 2, 1]),说明当前已是全局最大排列,此时直接整体反转即可得到最小排列,完成「循环」回到起点。

使用要点:

  • 边界处理 :i 从 n - 2 开始向前扫描,避免越界;j 从 n - 1 开始向前寻找,保证找到的是「刚好大于」的元素。
  • 比较符号 :寻找升序对时用 nums[i] >= nums[i + 1] 跳过相等元素(相等不构成严格升序);寻找交换对象时用 nums[j] <= nums[i] 跳过,确保严格大于。
  • 反转区间 :reverse(nums.begin() + i + 1, nums.end()) 统一处理「找到 i」和「整体降序」两种情况,代码简洁。
  • 结果返回 :本题为原地修改,无需返回值,直接操作 nums 即可。

算法变体与扩展:

  • 上一个排列(Previous Permutation):与本题对称,改为「从后向前找第一个降序对」并交换「刚好小于」的元素,思路完全一致。
  • 排列的字典序排名(LeetCode 60):给定排列求其字典序排名,或给定排名求排列,本质是「康托展开」与「逆康托展开」,与本题共享字典序的核心思想。
  • 全排列(LeetCode 46):回溯法枚举所有排列,与本题的「单步求下一个」形成对比------前者求全集,后者求后继。
  • 第 k 个排列(LeetCode 60) :在字典序全排列中直接定位第 k 个,可用「阶乘数系统」跳过大量枚举,与本题的字典序规则一脉相承。相关 LeetCode 例题:
相关推荐
(Charon)2 小时前
【C++面试】迭代器失效是什么?vector、list、map中如何避免迭代器失效
c++·算法·面试
驭渊的小故事3 小时前
NC229023 题解:二分答案求解“最大化最小值“问题
java·算法
朝朝辞暮i3 小时前
C++ 第 36 课:Action——机械臂/VLA 非常重要
开发语言·c++·算法·ros2
ra90fy3 小时前
B3873 [GESP202309 六级] 小杨买饮料
c++·算法
Omics Pro3 小时前
北理工李荣华:AI虚拟细胞证据多模态推理基准
数据库·人工智能·算法·机器学习·自然语言处理
xieliyu.3 小时前
LeetCode 1419 数青蛙:状态模拟求最少青蛙数量|Java 题解
java·算法·leetcode
acd120093 小时前
线上偶发 499 错误,并非后端服务异常
算法
Nil2083 小时前
leetcode 45跳跃游戏Ⅱ
算法·leetcode·游戏
小蒋观天下4 小时前
端侧大模型在安防摄像头部署实操(下篇)|模型量化、推理加速、视频接入与量产调优
大数据·人工智能·算法·安全·机器学习·计算机视觉·ai大模型