LeetCode-031:下一个排列,从右往左找“转折点”,再反转后缀

本题在线练习:LeetCode 31. 下一个排列 - 在线练习(免费 · 无需登录 · AI 辅助)(https://onefly.top/zero2Leetcode/playground.html?id=31)

配套刷题网站 Zero2Leetcode —— 内置本地 OJ + AI 教练,零门槛开始刷 Hot 100。

题目概述

给定一个整数数组 nums,将其重新排列成“字典序”下一个更大的排列。如果不存在更大的排列(已经是最大排列),则变成最小排列(升序)。

要求 原地修改,额外空间 O(1)

核心思路:下一个排列只需要“动最右边能动的地方”

字典序的规则是:越靠左的位置越重要,想得到“刚好大一点”的排列,应该尽量让左侧保持不变,只在右侧做最小改动。

步骤(经典三步):

  1. 从右往左找第一个位置 i,满足 nums[i] < nums[i+1],这叫“转折点/下降点”的前一个位置。
  2. 如果找到了 i,再从右往左找第一个 j,满足 nums[j] > nums[i],交换 nums[i]nums[j]
  3. i+1 到末尾这一段 反转(因为这一段原本是非递增的,反转后变成最小的升序后缀)。

如果第 1 步找不到 i,说明整个数组是非递增的,已经是最大排列,直接整体反转得到最小排列。

代码实现(原地)

class Solution:
    def nextPermutation(self, nums: list[int]) -> None:
        n = len(nums)

        # 1) find pivot: first i from right with nums[i] < nums[i+1]
        i = n - 2
        while i >= 0 and nums[i] >= nums[i + 1]:
            i -= 1

        if i >= 0:
            # 2) find rightmost j with nums[j] > nums[i]
            j = n - 1
            while nums[j] <= nums[i]:
                j -= 1
            nums[i], nums[j] = nums[j], nums[i]

        # 3) reverse suffix i+1..end
        l, r = i + 1, n - 1
        while l < r:
            nums[l], nums[r] = nums[r], nums[l]
            l += 1
            r -= 1

逐行拆解:为什么后缀要“反转”

在第 1 步找到的 i,意味着 i+1..end 这一段是 非递增 的:

... nums[i] < nums[i+1] >= nums[i+2] >= ... >= nums[n-1]

想让整体“刚好大一点”,把 nums[i] 换成后缀里“比它大一点点”的元素(第 2 步取最右的那个),然后让后缀变成最小可能(升序)。

由于后缀本来是非递增的,最省事的升序方式就是直接反转。

手动模拟:nums = [1,2,3]

  • i2 < 3,所以 i=1
  • j:从右找第一个 >2 的是 3,交换得到 [1,3,2]
  • 反转后缀(位置 2 到末尾)不变

答案 [1,3,2]

再看最大排列 [3,2,1]

  • 找不到 i(全程非递增)
  • 直接反转得到 [1,2,3]

复杂度分析

  • 时间:O(n)(最多三趟线性扫描/交换)
  • 空间:O(1)

总结

下一个排列的关键是“最右侧最小改动”:

  1. 找转折点 i
  2. 在右侧找最合适的 j 交换
  3. 反转后缀得到最小后缀

掌握这三步,基本不会写错。

posted @ 2026-03-27 01:22  孤飞  阅读(13)  评论(0)    收藏  举报