当前位置: 首页 > news >正文

【LeetCode 热题 100】31. 下一个排列

Problem: 31. 下一个排列

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(N)
    • 空间复杂度:O(1)

整体思路

这段代码旨在解决经典的 “下一个排列” (Next Permutation) 问题。问题要求重新排列一个整数数组,使其变为字典序上的下一个更大的排列。如果不存在更大的排列(即数组已经是降序排列),则将其重新排列为最小的排列(即升序排列)。这个操作必须在原地完成,只使用常数级的额外空间。

该算法是一种非常精巧的、基于观察的单次遍历解法。其核心思想是,要找到下一个更大的排列,我们需要从右向左找到第一个“破坏”降序趋势的数字,然后用一个比它稍大的数替换它,并对后面的部分进行排序以得到最小的增量。

算法的逻辑步骤可以分解为以下四步:

  1. 从右向左查找第一个“升序”对 (找到“小数”)

    • 算法从数组的倒数第二个元素 (i = n - 2) 开始向左扫描。
    • 它寻找第一个满足 nums[i] < nums[i + 1] 的索引 i
    • 为什么? 从右边看,只要 nums[i] >= nums[i+1] 持续成立,说明 [i, n-1] 这个后缀是一个降序(或非增序)序列。一个降序序列已经是它所能构成的最大排列。为了找到一个更大的排列,我们必须在更左边找到一个可以增大的“枢轴点”。这个 nums[i] 就是这个枢轴点,我们称之为“小数”。
  2. 从右向左查找第一个比“小数”大的数 (找到“大数”)

    • 在找到 i 之后,算法再从数组的最右端 (j = n - 1) 开始向左扫描。
    • 它寻找第一个满足 nums[j] > nums[i] 的索引 j
    • 为什么? 我们需要用一个比 nums[i] 大的数来替换它,以确保新的排列比旧的大。为了让这个增量尽可能小(以得到“下一个”排列),我们应该选择那个在后缀 [i+1, n-1] 中比 nums[i] 大的数里面最小的那个。由于后缀 [i+1, n-1] 是降序的,所以从右向左找到的第一个比 nums[i] 大的数 nums[j] 恰好就是这个“稍大”的数。
  3. 交换“小数”和“大数”

    • 找到 ij 后,交换 nums[i]nums[j] 的值。
    • 效果:此时,[0, i] 这部分已经确保了新的排列比原来的大。
  4. 反转“小数”位置之后的所有数

    • 在交换之后,后缀 [i+1, n-1] 仍然保持降序。
    • 为什么? 为了得到紧邻的下一个排列,我们需要让 [i+1, n-1] 这部分变为它所能构成的最小排列。一个降序序列的最小排列就是将其变为升序。
    • 因此,算法将 [i+1, n-1] 这个区间进行反转,使其变为升序。
  5. 特殊情况

    • 如果步骤1中的 while 循环结束后 i 变成了负数,这说明整个数组是完全降序的(如 [3, 2, 1])。此时不存在更大的排列,算法会跳过步骤2和3,直接执行步骤4,反转整个数组(从索引0开始),得到最小的排列(升序)。

完整代码

class Solution {/*** 找到并原地修改数组为下一个更大的字典序排列。* @param nums 整数数组*/public void nextPermutation(int[] nums) {int n = nums.length;// 步骤 1: 从右向左查找第一个“升序”对 (nums[i] < nums[i+1])// i 是这个“小数”的索引。int i = n - 2;while (i >= 0 && nums[i] >= nums[i + 1]) {i--;}// 如果 i >= 0, 说明找到了这样的“小数”,即数组不是完全降序的。if (i >= 0) {// 步骤 2: 从右向左查找第一个比 nums[i] 大的数// j 是这个“大数”的索引。int j = n - 1;while (nums[i] >= nums[j]) {j--;}// 步骤 3: 交换“小数”和“大数”swap(nums, i, j);}// 步骤 4: 反转“小数”位置之后的所有数// 如果 i < 0 (数组完全降序),这将反转整个数组,得到最小排列。// 否则,这将使交换后的后缀变为最小排列。reverse(nums, i + 1, n - 1);}/*** 辅助函数:交换数组中两个索引位置的元素。*/private void swap(int[] nums, int i, int j) {int t = nums[i];nums[i] = nums[j];nums[j] = t;}/*** 辅助函数:原地反转数组的指定区间 [left, right]。*/private void reverse(int[] nums, int left, int right) {while (left < right) {swap(nums, left++, right--);}}
}

时空复杂度

时间复杂度:O(N)

  1. 查找 i:第一个 while 循环最多扫描整个数组一次。在最坏情况下,时间复杂度为 O(N)
  2. 查找 j:第二个 while 循环最多扫描整个数组一次。在最坏情况下,时间复杂度为 O(N)
  3. 交换swap 操作是 O(1)
  4. 反转reverse 函数最多需要遍历 N/2 次交换,其操作的元素数量与 N 呈线性关系。在最坏情况下,时间复杂度为 O(N)

综合分析
整个算法由几次独立的、不嵌套的线性扫描组成。总的时间复杂度是 O(N) + O(N) + O(N) = O(N)

空间复杂度:O(1)

  1. 主要存储开销:该算法没有创建任何与输入规模 N 成比例的新的数据结构。
  2. 辅助变量:只使用了 n, i, j, t, left, right 等几个固定数量的整型变量。

综合分析
算法的所有操作都是在原数组上进行的(in-place),所需的额外辅助空间是常数级别的。因此,其空间复杂度为 O(1)

参考灵神

http://www.xdnf.cn/news/1369027.html

相关文章:

  • 进入docker中mysql容器的方法
  • Linux(二十二)——服务器初始化指南
  • 把 shell 脚本里的「后台接收」-- 以 UART/CAN 双总线监听为例
  • 影响服务器托管费用的因素​
  • 论文阅读-CompletionFormer
  • 山中游玩播报
  • 简单聊聊光栅化技术
  • 虚拟机中kubeadim部署的k8s集群,虚拟机关机了,重新开机后集群状态能否正常恢复的两种可能(详解)
  • vue2 创建threejs场景
  • ubuntu20.04 终端安装claude
  • 事件驱动架构详解
  • .gitignore 文件相关使用配置
  • 服务器数据恢复—热备盘上线失败如何恢复数据?
  • Ansible 自动化运维工具:介绍与完整部署(RHEL 9)
  • 如何基于阿里云OpenSearch LLM搭建智能客服平台
  • 亚马逊类目合规风暴:高压清洗机品类整顿背后的运营重构与风险防御
  • 零基础构建MCP服务器TypeScriptPython双语言实战指南
  • 零基础也能照做的WordPress网站安全漏洞修复 + 高级优化保姆级教程。
  • 【JavaEE】了解volatile和wait、notify(三)
  • 算法题打卡力扣第209题:长度最小的子数组(mid)
  • 【强化学习】区分理解: 时序差分(TD)、蒙特卡洛(MC)、动态规划(DP)
  • THM El Bandito
  • 使用C++与Qt6,在windows上打造MacOS风格桌面应用窗口
  • SELinux
  • Mac测试端口连接的几种方式
  • 【制作100个Unity游戏】从零开始构建类《月圆之夜》《杀戮尖塔》的卡牌游戏(附带项目源码)
  • CSS 结构伪类选择器
  • C语言开发入门教程:从环境搭建到第一个程序
  • 【lucene】SpanNotQuery 存在的意义
  • 国产化Excel开发组件Spire.XLS教程:Python 读取 CSV 文件,从基础到进阶指南