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

【LeetCode 热题 100】25. K 个一组翻转链表——迭代+哨兵

Problem: 25. K 个一组翻转链表
题目:给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

文章目录

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

整体思路

这段代码旨在解决一个高难度的经典链表问题:K 个一组翻转链表 (Reverse Nodes in k-Group)。问题要求将一个链表中的节点每 k 个一组进行翻转,如果最后剩余的节点数不足 k,则保持原样。

该算法采用了一种精巧的 迭代 方法,通过分组处理和局部反转来完成任务。它结合了 哨兵节点 和多个指针,以清晰地管理复杂的链接关系。

其核心逻辑步骤如下:

  1. 预计算链表长度

    • 算法首先通过一次遍历计算出链表的总长度 n
    • 目的:这使得在主循环中可以方便地判断剩余的节点是否还足够形成一个大小为 k 的组,即 while (n >= k)
  2. 初始化

    • 哨兵节点:创建一个 dummy 节点,其 next 指向原始头节点 head。这极大地简化了对链表头部的修改,因为第一组的反转与其他组的逻辑可以完全统一。
    • 关键指针
      • groupPrev: 指向当前待反转组的前一个节点。它负责将处理好的前一部分链表与当前反转好的组连接起来。初始时指向 dummy
      • pre: 用于在组内进行反转的指针,初始为 null
      • cur: 指向在组内当前正在处理的节点,初始为 head
  3. 分组迭代与反转

    • 算法进入一个主 while 循环,只要剩余节点数 n 大于等于 k,就处理一个组。
    • 组内反转
      • 在主循环内部,一个 for 循环执行 k 次,用于反转当前组的 k 个节点。
      • 这部分使用了标准的迭代反转链表的技巧:通过 precur 指针,逐个将 cur 节点的 next 指向前一个节点 pre,从而实现局部反转。
      • for 循环结束后,pre 指向了该组反转后的新头节点,而 cur 指向了下一组的起始节点。
    • 重新链接
      • 这是算法最精妙的部分。反转一个组后,需要将其正确地接回主链表中。
      • preTail = groupPrev.next: 首先,找到反转前该组的头节点,这个节点在反转后会成为尾节点。我们将其保存为 preTail
      • preTail.next = cur: 将反转后的组的尾部 (preTail) 连接到下一组的头部 (cur)。
      • groupPrev.next = pre: 将前一个组的尾部 (groupPrev) 连接到当前反转后组的新头部 (pre)。
      • groupPrev = preTail: 为下一轮循环做准备,将 groupPrev 指针移动到刚刚处理完的组的尾节点上。
  4. 返回结果

    • 循环结束后,所有满足条件的组都已被反转并正确链接。dummy.next 指向了整个新链表的头节点,将其返回。

完整代码

/*** Definition for singly-linked list.*/
class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) { this.val = val; }ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}class Solution {/*** K 个一组翻转链表。* @param head 原始链表的头节点* @param k 每组的节点数* @return 翻转后链表的头节点*/public ListNode reverseKGroup(ListNode head, int k) {// 步骤 1: 预计算链表的总长度 nint n = 0;ListNode cur = head;while (null != cur) {n++;cur = cur.next;}// 创建哨兵节点,简化头部操作ListNode dummy = new ListNode(0, head);// groupPrev 指向当前待反转组的前一个节点ListNode groupPrev = dummy;// pre 用于组内反转,将成为反转后组的新头ListNode pre = null;// cur 指向组内当前正在处理的节点cur = head;// 主循环:只要剩余节点数足够 k 个,就处理一个组while (n >= k) {// 更新剩余节点数n -= k;// 步骤 2: 核心反转逻辑,反转 k 个节点for (int i = 0; i < k; i++) {ListNode nxt = cur.next; // 暂存下一个节点cur.next = pre;          // 当前节点指向前一个节点pre = cur;               // pre 前移cur = nxt;               // cur 前移}// 步骤 3: 重新链接反转后的组// preTail 是反转前该组的头节点,反转后它变成了尾节点ListNode preTail = groupPrev.next;// 将反转后组的尾部 (preTail) 连接到下一组的头部 (cur)preTail.next = cur;// 将前一个组的尾部 (groupPrev) 连接到反转后组的新头部 (pre)groupPrev.next = pre;// 为下一次循环做准备:更新 groupPrev,它现在是刚刚处理完的组的尾节点groupPrev = preTail;}return dummy.next;}
}

时空复杂度

时间复杂度:O(N)

  1. 计算长度:第一个 while 循环遍历整个链表一次,时间复杂度为 O(N)
  2. 分组反转:主 while 循环和内部的 for 循环结合起来,确保了每个节点只会被访问和操作常数次。while 循环执行 N/k 次,for 循环执行 k 次,总操作数与 (N/k) * k = N 成正比。因此,这部分的时间复杂度也是 O(N)

综合分析
算法的总时间复杂度是 O(N) + O(N) = O(N)。它对链表进行了两次线性扫描(一次计数,一次反转)。因此,最终时间复杂度为 O(N)

空间复杂度:O(1)

  1. 主要存储开销:算法只使用了 dummy, groupPrev, pre, cur, nxt, preTail 等固定数量的指针变量。
  2. 空间大小:这些变量的数量不随输入链表的长度 N 或分组大小 k 的变化而改变。

综合分析
算法没有使用任何与输入规模成比例的额外数据结构。因此,其额外辅助空间复杂度为 O(1)。这是一个高效的原地算法。

参考灵神

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

相关文章:

  • 【YOLOv8-obb部署至RK3588】模型训练→转换RKNN→开发板部署
  • Jenkins+Gitee+Docker容器化部署
  • super task 事件驱动框架
  • 用AI做带货视频评论分析【Datawhale AI 夏令营】
  • 冒泡排序和快速排序
  • 「Linux命令基础」文本模式系统关闭与重启
  • 【C/C++】动态内存分配:从 C++98 裸指针到现代策略
  • Linux操作系统之进程间通信:命名管道
  • 飞算JavaAI:给Java开发装上“智能引擎”的超级助手
  • vue入门学习教程
  • 车载诊断进阶篇 --- 关于网关转发性能引起的思考
  • 匿名函数作递归函数引用
  • uniapp制作一个视频播放页面
  • C++11中的std::minmax与std::minmax_element:原理解析与实战
  • WIFI协议全解析06:Beacon帧、Probe帧你必须懂,搞WiFi通信绕不开它们
  • 【理念●体系】Windows AI 开发环境搭建实录:六层架构的逐步实现与路径治理指南
  • SEQUENCE在RAC多实例开启CACHE的NEXTVAL数值乱序问题
  • 从代码学习深度强化学习 - PPO PyTorch版
  • Go语言WebSocket编程:从零打造实时通信利器
  • Linux操作系统从入门到实战:怎么查看,删除,更新本地的软件镜像源
  • 蔚来测开一面:HashMap从1.7开始到1.8的过程,既然都解决不了并发安全问题,为什么还要进一步解决环形链表的问题?
  • Spring的事务控制——学习历程
  • HarmonyOS NEXT端云一体化开发初体验
  • [Dify] -基础入门4-快速创建你的第一个 Chat 应用
  • 牛客:HJ17 坐标移动[华为机考][字符串]
  • Leaflet面试题及答案(1-20)
  • [实战]调频三角波和锯齿波信号生成(完整C代码)
  • 深入浅出:什么是MCP(模型上下文协议)?
  • 力扣网编程134题:加油站(双指针)
  • C++中柔性数组的现代化替代方案:从内存布局优化到标准演进