25. K 个一组翻转链表

25. K 个一组翻转链表

困难

给你链表的头节点 head ,每 k个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

复制代码
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2:

复制代码
输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

提示:

  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

进阶: 你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?

📝 核心笔记:K 个一组翻转链表 (Reverse Nodes in k-Group)

1. 核心思想 (一句话总结)

"先数数,够数就翻,翻完缝合。" 这道题本质上是 LeetCode 206 (反转链表)LeetCode 92 (反转链表 II) 的结合体。 我们需要维护一个"哨兵" p0,它始终站在 "当前要翻转的这一组" 的前一个位置。

  • 组内:标准的链表翻转(局部倒序)。
  • 组间:翻转后,原来的头变尾,原来的尾变头,需要把它们和前后段重新接上(缝合)。
2. 算法流程 (三步走)
  1. 统计长度 (Count)
    • 先遍历一遍链表,得到总长度 n
    • 作用:决定循环次数。只有当剩余节点数 n >= k 时才翻转,不够 k 个直接保留不动。
  1. 组内翻转 (Reverse Inner)
    • 循环 k 次,执行标准的"反转链表"逻辑。
    • 翻转结束后,pre 变成了当前组的新头,cur 变成了下一组的开头。
  1. 缝合接口 (Connect)
    • 这是最难的一步。我们需要三个节点参与缝合:
      • p0:当前组的前驱(哨兵)。
      • p0.next:当前组 原来的头 (翻转后变成了 现在的尾)。
      • cur:下一组的头。
      • pre:当前组 现在的头
    • 连接逻辑
      1. p0.next.next = cur:让本组现在的尾巴,连上下一组的头。
      2. p0.next = pre:让哨兵连上本组现在的头。
      3. p0 = ...:哨兵走到本组现在的尾巴位置,准备下一轮。
🔍 代码回忆清单
复制代码
// 题目:LC 25. Reverse Nodes in k-Group
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        // 1. 统计节点总数
        int n = 0;
        for (ListNode cur = head; cur != null; cur = cur.next) {
            n++;
        }

        ListNode dummy = new ListNode(0, head);
        ListNode p0 = dummy; // p0 是每一组的前驱节点 (哨兵)
        
        // cur 和 pre 放在外面初始化,因为它们在循环之间是传递的
        ListNode pre = null;
        ListNode cur = head;

        // 2. 循环处理每一组
        // 只要剩余节点数 n >= k,就进行翻转
        for (; n >= k; n -= k) {
            
            // 3. 组内翻转 (标准的 LC 206 逻辑)
            // 翻转 k 个节点
            for (int i = 0; i < k; i++) { 
                ListNode nxt = cur.next;
                cur.next = pre; 
                pre = cur;
                cur = nxt;
            }
            // 循环结束后:
            // pre 是当前组的新头
            // cur 是下一组的头 (未处理的第一个)
            
            // 4. 缝合 (核心难点)
            // p0.next 是这一组"原来的头",翻转后它变成了"现在的尾"
            ListNode nxt = p0.next; // 暂存一下"现在的尾"
            
            // 现在的尾 -> 指向下一组的头
            // (对应图解:1 连向 3)
            p0.next.next = cur; 
            
            // 哨兵 -> 指向当前组的新头
            // (对应图解:dummy 连向 2)
            p0.next = pre; 
            
            // 5. 更新 p0
            // p0 走到当前组的尾部,作为下一组的哨兵
            p0 = nxt; 
        }
        
        return dummy.next;
    }
}
⚡ 快速复习 CheckList (易错点)
  • \] **为什么要有** **n****?**

    • 如果不统计 n,你很难判断最后剩下的节点是否足 k 个。如果不足 k 个却翻转了,就不符合题意了。
    • n >= k 是循环继续的硬性条件。
  • \] **缝合顺序乱了?**

    • 口诀:先连尾巴,再连头。
    • 先让 旧头(新尾) 连上 cur (下一段)。
    • 再让 哨兵 连上 pre (新头)。
  • \] **p0 怎么移动?**

    • p0 必须移动到当前子链表的末尾。
    • 也就是翻转前的 head,翻转后的 tail。代码里巧妙地利用 p0.next(在修改前)获取了这个节点。
🖼️ 数字演练

链表:1 -> 2 -> 3 -> 4 -> 5k = 2``n = 5, dummy -> 1. p0 = dummy.

Round 1 (处理 1, 2):

  • Inner Loop:
    • 翻转后:2 -> 1
    • pre 指向 2,cur 指向 3。
  • Wiring (缝合):
    • p0.next 是 1 (旧头/新尾)。
    • 1.next = 3 (连上下一个组)。
    • dummy.next = 2 (哨兵连新头)。
    • 链表变为:dummy -> 2 -> 1 -> 3 -> 4 -> 5.
  • Update : p0 移动到 1。

Round 2 (处理 3, 4):

  • Inner Loop:
    • 翻转后:4 -> 3
    • pre 指向 4,cur 指向 5。
  • Wiring:
    • p0.next 是 3。
    • 3.next = 5.
    • 1.next = 4.
    • 链表变为:dummy -> 2 -> 1 -> 4 -> 3 -> 5.
  • Update : p0 移动到 3。

End : n=11 < 2,循环结束。剩下的 5 不动。

相关推荐
段子子2 小时前
【使用MQTT】
java
坐在地上想成仙2 小时前
从机床到键盘:用机械设计思维写出一个可部署网页
java·c++·python
ros2752292 小时前
idea & gitee 使用教程
java·gitee·intellij-idea
叫码农就行2 小时前
spring cloud 笔记
java·笔记·spring cloud
CoderCodingNo2 小时前
【GESP】C++五级练习题 luogu-P2242 公路维修问题
开发语言·c++·算法
青云交2 小时前
Java 大视界 -- Java 大数据机器学习模型在电商商品推荐系统中的冷启动问题攻克与个性化推荐强化
java
WZTTMoon2 小时前
Spring Boot 为何不推荐使用@Autowired
java·spring boot·spring
输出输入2 小时前
IJ IDEA支持中文变量名、方法名、类名吗?
java·intellij-idea
阿华hhh2 小时前
day2(IMX6ULL)<led(c语言版)>
java·c语言·jvm