LeetCode-031反转后缀,如何构造?
摘要:本题在线练习:LeetCode 31. 下一个排列 - 在线练习(免费 · 无需登录 · AI 辅助)(https:onefly.topzero2Leetcodeplaygroun
本题在线练习:LeetCode 31. 下一个排列 - 在线练习(免费 · 无需登录 · AI 辅助)(https://onefly.top/zero2Leetcode/playground.html?id=31)
配套刷题网站 Zero2Leetcode —— 内置本地 OJ + AI 教练,零门槛开始刷 Hot 100。
题目概述
给定一个整数数组 nums,将其重新排列成“字典序”下一个更大的排列。如果不存在更大的排列(已经是最大排列),则变成最小排列(升序)。
要求 原地修改,额外空间 O(1)。
核心思路:下一个排列只需要“动最右边能动的地方”
字典序的规则是:越靠左的位置越重要,想得到“刚好大一点”的排列,应该尽量让左侧保持不变,只在右侧做最小改动。
步骤(经典三步):
从右往左找第一个位置 i,满足 nums[i] < nums[i+1],这叫“转折点/下降点”的前一个位置。
如果找到了 i,再从右往左找第一个 j,满足 nums[j] > nums[i],交换 nums[i] 与 nums[j]。
把 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]
找 i:2 < 3,所以 i=1
找 j:从右找第一个 >2 的是 3,交换得到 [1,3,2]
反转后缀(位置 2 到末尾)不变
答案 [1,3,2]。
再看最大排列 [3,2,1]:
找不到 i(全程非递增)
直接反转得到 [1,2,3]
复杂度分析
时间:O(n)(最多三趟线性扫描/交换)
空间:O(1)
总结
下一个排列的关键是“最右侧最小改动”:
找转折点 i
在右侧找最合适的 j 交换
反转后缀得到最小后缀
掌握这三步,基本不会写错。
