1. 题目解析与核心思路
"下一个排列"是力扣hot100系列中的经典算法题(编号56),要求给定一个数字序列,找出其字典序中下一个更大的排列。如果已经是最大排列,则返回最小排列。例如:
- 输入[1,2,3] → 输出[1,3,2]
- 输入[3,2,1] → 输出[1,2,3]
这个问题的核心在于理解排列的字典序生成规则。字典序排列的本质是从左到右比较数字大小,例如123 < 132 < 213 < 231 < 312 < 321。要找到下一个更大的排列,需要从右向左找到第一个破坏递增顺序的数字,然后将其与右侧刚好大于它的数字交换,最后反转剩余部分。
关键理解:字典序排列可以看作数字组成的"单词"在字典中的排序,找下一个排列相当于找这个"单词"的下一个字母组合。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法步骤拆解
2.1 寻找转折点
从数组末尾开始向前扫描,找到第一个满足nums[i] < nums[i+1]的位置i。这个位置标志着排列可以增大的最右侧机会点。如果没有找到这样的i,说明整个数组是降序排列(已经是最大排列),直接反转整个数组即可。
python复制def nextPermutation(nums):
n = len(nums)
i = n - 2
while i >= 0 and nums[i] >= nums[i+1]:
i -= 1
2.2 寻找交换对象
如果找到了转折点i,需要从末尾开始找到第一个大于nums[i]的数字nums[j],然后交换这两个数字。因为i右侧是降序排列,所以从右向左找到的第一个nums[j] > nums[i]就是刚好比nums[i]大的最小数字。
python复制 if i >= 0:
j = n - 1
while j >= 0 and nums[j] <= nums[i]:
j -= 1
nums[i], nums[j] = nums[j], nums[i]
2.3 反转剩余部分
交换后,i右侧的数字仍然是降序排列。为了得到刚好大于原排列的最小排列,需要将这部分反转变为升序。
python复制 left, right = i+1, n-1
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
3. 完整代码实现
将上述步骤组合起来,得到完整的Python解决方案:
python复制def nextPermutation(nums):
n = len(nums)
# Step 1: 从后向前找第一个升序对
i = n - 2
while i >= 0 and nums[i] >= nums[i+1]:
i -= 1
# Step 2: 如果找到转折点
if i >= 0:
j = n - 1
while j >= 0 and nums[j] <= nums[i]:
j -= 1
nums[i], nums[j] = nums[j], nums[i]
# Step 3: 反转i+1到末尾
left, right = i+1, n-1
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
时间复杂度分析:
- 三次线性扫描:O(n)
- 空间复杂度:O(1)原地修改
4. 边界情况与测试用例
4.1 典型测试用例
python复制测试用例1:[1,2,3] → [1,3,2]
测试用例2:[3,2,1] → [1,2,3]
测试用例3:[1,1,5] → [1,5,1]
测试用例4:[1,3,2] → [2,1,3]
4.2 特殊边界情况
- 单元素数组:[1] → [1]
- 全相同元素:[2,2,2] → [2,2,2]
- 已经最大排列:[3,2,1] → [1,2,3]
- 长数组测试:[1,3,2,5,4,2] → [1,3,2,5,2,4]
5. 常见错误与调试技巧
5.1 典型错误模式
- 转折点寻找错误:使用nums[i] > nums[i+1]而非>=,会错过重复元素情况
- 交换对象选择错误:没有从右向左找第一个大于nums[i]的数,导致交换后不是最小增量
- 反转范围错误:错误地反转整个数组而非i+1到末尾
5.2 调试建议
- 打印关键步骤的变量值:
python复制print(f"i={i}, nums[i]={nums[i]}")
print(f"j={j}, nums[j]={nums[j]}")
print("After swap:", nums)
print("After reverse:", nums)
- 可视化排列变化:在纸上画出数字变化过程,特别是转折点和交换点的位置
6. 算法优化与变种
6.1 性能优化
虽然算法已经是O(n)时间复杂度,但在实际编码竞赛中可以:
- 使用更简洁的反转写法:nums[i+1:] = nums[i+1:][::-1]
- 合并部分条件判断减少代码行数
6.2 相关变种题目
- 上一个排列:找字典序中前一个更小的排列
- 第k个排列:给定n和k,返回1..n组成的第k个排列
- 排列序列:生成所有排列中的特定序位
7. 实际应用场景
排列生成算法在以下场景有实际应用:
- 密码破解中的暴力尝试顺序
- 游戏中的关卡排列组合
- 数据加密中的密钥生成
- 测试用例生成中的输入排序
我在实际工作中曾用类似算法解决过一个产品配置组合问题,需要生成所有可能的参数排列并找到最优解。理解排列生成原理帮助我设计出了高效的搜索策略,将原本需要数小时的计算缩短到几分钟内完成。
8. 学习路线建议
要掌握这类排列组合问题,建议的学习路径:
- 先理解全排列的递归生成方法
- 掌握字典序排列的性质和规律
- 练习手动计算多个例子的下一个排列
- 实现算法并测试各种边界情况
- 尝试解决相关变种题目巩固理解
对于力扣hot100的刷题,建议每天专注1-2题,不仅要写出解法,更要理解背后的数学原理和可能的优化空间。这道"下一个排列"题虽然标为中等难度,但包含了数组操作、双指针、数学分析等多个重要编程概念,值得反复练习直到能够闭眼写出无bug的代码。
