在链表题里,如果说有一道题能真正检验你对「指针」的理解深度,那 K 个一组翻转链表一定榜上有名。
很多人第一次看这题的感受是:
- 思路好像懂了
- 代码能抄出来
- 但指针一动,脑子就乱
这篇笔记,我想做一件事:
把这道题拆到你能“在脑子里完整跑一遍指针”为止。
一、题目要求回顾
给你一个链表,每 k 个节点一组进行翻转:
- k 个一组必须完整,才能翻转
- 如果最后剩下的节点不足 k 个,保持原顺序
- 只能使用 O(1) 额外空间
示例:
输入:1 -> 2 -> 3 -> 4 -> 5, k = 2
输出:2 -> 1 -> 4 -> 3 -> 5
二、这道题的本质是什么?
一句话概括:
在链表中,反复对固定长度为 k 的区间做局部翻转
所以问题被拆成了三件事:
- 如何判断后面是否还有 k 个节点?
- 如何只翻转
[start, end]这一段? - 翻转后,如何把链表重新接回去?
三、为什么一定要用 dummy 节点?
这是链表高质量解法的标配。
ListNode dummy = new ListNode(0);
dummy.next = head;
原因只有一个:
- 第一组翻转时,头节点会发生变化
- 使用 dummy,可以 把“头节点”当成普通节点处理
- 所有翻转逻辑完全一致,不需要特殊判断
四、核心思路总览
整体代码是一个「按组处理」的循环:
while (true) {
1. 判断是否够 k 个
2. 翻转当前这 k 个
3. 移动 pre,进入下一组
}
关键指针只有三个:
pre:当前要翻转这一组的前驱节点kth:当前组的第 k 个节点nextGroupHead:下一组的起点
五、完整代码
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;
while (true) {
// 1. 找到第 k 个节点
ListNode kth = pre;
for (int i = 0; i < k; i++) {
kth = kth.next;
if (kth == null) {
return dummy.next;
}
}
// 2. 记录下一组的起点
ListNode nextGroupHead = kth.next;
// 3. 翻转当前这 k 个节点
ListNode prev = nextGroupHead;
ListNode curr = pre.next;
while (curr != nextGroupHead) {
ListNode temp = curr.next;
curr.next = prev;
prev = curr;
curr = temp;
}
// 4. 重新接回链表
ListNode newHead = pre.next;
pre.next = kth;
pre = newHead;
}
}
}
六、如何判断“是否够 k 个”?
这一段是只检查,不修改链表:
ListNode kth = pre;
for (int i = 0; i < k; i++) {
kth = kth.next;
if (kth == null) {
return dummy.next;
}
}
含义很明确:
- 从
pre出发向后走 k 步 - 中途遇到 null,说明不足 k 个
- 不翻转,直接返回结果
七、局部翻转的精髓:为什么 prev 要指向 nextGroupHead?
ListNode prev = nextGroupHead;
ListNode curr = pre.next;
这一点非常关键。
我们翻转的是:
[pre.next ... kth]
但让 prev 一开始指向 nextGroupHead,意味着:
- 翻转完成后,尾节点会自动接到下一组
- 不需要额外处理尾指针
- 翻转逻辑和「反转整个链表」完全一致
这是一个非常高级但非常稳的技巧。
八、最容易让人懵的三行代码
ListNode newHead = pre.next;
pre.next = kth;
pre = newHead;
逐行解释:
newHead = pre.next
翻转前的头节点,翻转后会变成 这一组的尾节点
pre.next = kth
把前驱节点接到翻转后的新头
pre = newHead
让 pre 移动到这一组的尾部,为下一轮做准备
关键理解一句话:
pre 是“游标”,不是 dummy 的别名
九、指针变化示意
以 k = 2 为例:
翻转前:
dummy -> 1 -> 2 -> 3 -> 4
翻转第一组后:
dummy -> 2 -> 1 -> 3 -> 4
↑
pre
第二轮自然处理 [3, 4],逻辑完全复用。
十、复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(1)
每个节点只被访问和翻转一次。
十一、小总结
这道题真正考察的不是“会不会翻转链表”,而是:
- 是否理解 指针在链表中的相对关系
- 是否能把“区间翻转”抽象成通用模板
- 是否能用 dummy + pre 把复杂情况统一掉
如果你能把这道题讲清楚,
80% 的链表题你都会发现:原来它们是一个套路。