K 个一组翻转链表:链表操作的“硬核”挑战
摘要:本文详解 LeetCode 25 题“K 个一组翻转链表”。通过“迭代法”和“递归法”两种视角,结合“虚拟头节点”与“子链表翻转”,在不修改节点值的前提下,实现每 K 个节点为一组的翻转,助你掌握链表操作的高阶技巧。
📚 核心知识点:子链表翻转与连接
这道题是链表操作的“集大成者”,它结合了反转链表和链表连接两个核心技巧。
关键概念:
- 子链表翻转:我们需要一个辅助函数,能够翻转从
start到end的这一段链表,并返回新的头节点。 - 连接艺术:翻转完一组后,需要将前一组的尾部连接到当前组的新头部,当前组的旧头部(翻转后的尾部)连接到下一组的头部。
- 虚拟头节点(Dummy Node) :处理头节点变化的必备神器,让第一组的翻转逻辑与后续组保持一致。
📝 题目解析:LeetCode 25. K 个一组翻转链表
题目描述:
给你链表的头节点 head,每 k 个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
💡 迭代法:步步为营的“工程派”
核心逻辑:
想象你在工厂流水线上工作。你的任务是把传送带上的零件每 k 个取下来,倒个序放回去,然后处理下一批。
步骤拆解:
-
检查数量:首先,我们要确认后面还有没有
k个节点。如果没有,直接返回,剩下的保持原样。 -
切断:找到这一组的结尾,把它和后面的链表暂时切断。
-
翻转:调用“反转链表”函数,翻转这一小段。
-
连接:
- 上一组的尾巴(
prev)连接这一组的新头(new_head)。 - 这一组的旧头(现在是尾巴)连接下一组的开头。
- 上一组的尾巴(
-
移动:更新
prev,准备处理下一组。
代码实现(Python) :
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
# 1. 虚拟头节点,永远指向链表头部,方便处理第一组
dummy = ListNode(0)
dummy.next = head
# prev 指向每一组的前驱节点
prev = dummy
while head:
# 2. 检查剩余节点是否足够 k 个
tail = prev
for i in range(k):
tail = tail.next
if not tail:
return dummy.next # 不足 k 个,直接返回结果
# 3. 记录下一组的开头,并切断当前组
next_group_head = tail.next
tail.next = None
# 4. 翻转当前组 (head 到 tail),并连接回主链表
# reverse 函数返回新的头节点(原 tail)和新的尾节点(原 head)
new_head, new_tail = self.reverse(head, tail)
# 连接:前驱 -> 新头
prev.next = new_head
# 连接:新尾 -> 下一组开头
new_tail.next = next_group_head
# 5. 更新指针,准备下一轮
prev = new_tail
head = next_group_head
return dummy.next
# 辅助函数:翻转从 head 到 tail 的链表,返回 (新头, 新尾)
def reverse(self, head: ListNode, tail: ListNode):
prev = tail.next # 翻转后的尾节点要指向下一组的开头
curr = head
while prev != tail:
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
return tail, head # 原 tail 变新头,原 head 变新尾
🚀 递归法:四两拨千斤的“魔法派”
核心逻辑:
递归的思路非常清晰,它把问题拆解为:
翻转前 K 个节点 + 剩下的节点交给递归去处理。
步骤拆解:
-
基准情况(Base Case) :如果剩下的节点不足
k个,直接返回head,不做任何处理。 -
递归步骤:
- 找到第
k个节点。 - 记录第
k+1个节点(next_group)。 - 切断第
k个节点和后面的联系。 - 翻转前
k个节点。 - 关键点:翻转后的尾节点(也就是原来的
head)的next,应该指向“递归处理next_group的结果”。
- 找到第
代码实现(Python) :
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
# 1. 检查剩余节点是否足够 k 个
curr = head
for i in range(k):
if not curr:
return head # 不足 k 个,直接返回,不翻转
curr = curr.next
# 2. 此时 curr 指向第 k+1 个节点
# 递归处理剩下的部分,返回的是剩下部分处理后的新头
rest_new_head = self.reverseKGroup(curr, k)
# 3. 翻转前 k 个节点
# 注意:这里翻转时,要把第 k 个节点的 next 指向 rest_new_head
new_head = self.reverseFirstK(head, k, rest_new_head)
return new_head
# 辅助函数:翻转前 k 个节点,并将尾部连接到 next_group
def reverseFirstK(self, head: ListNode, k: int, next_group: ListNode) -> ListNode:
prev = next_group
curr = head
for _ in range(k):
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
return prev # 返回新的头节点
📌 总结
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 迭代法 | 空间复杂度 O(1),逻辑严谨,像工程师一样步步为营。 | 代码量大,指针操作繁琐,容易写错。 | 面试中要求空间复杂度严格为 O(1) 时。 |
| 递归法 | 代码简洁,逻辑清晰,符合人类直觉。 | 需要额外的栈空间,空间复杂度 O(N/k)。 | 面试中允许使用额外空间,或者追求代码优雅时。 |
这道题是链表操作的试金石,建议先手写迭代法理解指针断连的细节,再尝试递归法体会分治思想。