关于《算法25. K 个一组翻转链表》的解题思路
《25. K 个一组翻转链表》的解法:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
# 需要理解变量指向节点和节点指向节点(.next)的区别
class Solution:def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:# 统计节点数n = 0cur = headwhile cur:n+=1cur=cur.next# 指向头节点的哑节点p0 = dummy =ListNode(next=head)# 引用前一个节点的变量pre = None# 引用当前节点的变量cur = head# k个一组处理while n>=k:n-=k# 反转k个节点的链表# 依次遍历只改变一个节点的指向for _ in range(k):# nxt指向cur的下一个变量nxt = cur.next# cur指向的节点的下一个节点指向prevcur.next = pre# 变量引用位置往前推进# pre 是这组反转后的新头节点pre = cur# cur 是下一组的开始cur = nxt# 反转k个节点链表之后,需要把反转后的链表与没有反转的链表联接起来# 未反转前的头节点,现在是尾节点nxt = p0.next # 反转后的尾节点和未反转的头节点联接起来nxt.next = cur# p0指向新的头节点p0.next = pre# p0移动到最新反转的链表的尾节点,为什么不指向新链表的头节点,可以类比最开始p0指向的是Nonep0 = nxtreturn dummy.next
我反转了k个节点,但是不知道怎么往后推近,上面代码是怎么能想到的呢
已经理解了如何反转一个长度为 k
的链表子段,但卡在了如何把反转后的子段重新连接回原链表,并推进到下一组。我们来一步步拆解你这段代码的思路,特别是那个“灵光一闪”的关键点:p0 = dummy
和 p0.next
的作用。
🌟 核心思想:用一个“前驱指针” p0
来维护上一组的尾节点
我们希望:
- 每次反转完一个
k
长度的子链表后,把这个反转后的子链表接回原链表。 - 然后移动到下一组继续处理。
难点在于:反转后,原来的头变成尾,原来的尾变成头,你怎么知道该把谁接到谁身上?
🔍 举个例子:
原链表:1 -> 2 -> 3 -> 4 -> 5,k = 2
第一组:1 -> 2 反转后变成 2 -> 1
我们要把 2 接到前面(比如 dummy),然后把 1 指向 3
所以需要一个指针记住“上一组的尾节点”,也就是下一组反转后要接在它后面。
🧠 关键变量解析
p0 = dummy = ListNode(next=head)
pre = None
cur = head
dummy
: 虚拟头节点,防止头节点被修改。p0
: 上一组的尾节点,也是当前组的前驱节点。
👉 所以当前这组反转完之后,要让p0.next
指向反转后的新头节点。
比如刚开始:
dummy -> 1 -> 2 -> 3 -> ...p0cur
🔁 处理每一组 k 个节点
while n >= k:n -= kfor _ in range(k):nxt = cur.nextcur.next = prepre = curcur = nxt
这段完成了对 k
个节点的反转。此时:
pre
是这组反转后的新头节点(原尾节点)cur
是下一组的开始(或 None)pre
到原来的cur
之间的链表已被反转
比如反转 1->2
后:
pre: 2 -> 1 (1.next 指向 None?不对,其实还没连好!)
cur: 指向 3
但 1.next 还是指向 None?怎么连回去?
这时候你发现:我们只是局部反转了,但没有和前后连接!
🧩 神来之笔:连接反转后的子链表
nxt = p0.next
nxt.next = cur
p0.next = pre
p0 = nxt
我们逐行分析:
1. nxt = p0.next
p0.next
是当前这组的原始头节点(反转前)- 反转后它变成了尾节点
- 我们要记住它,因为下一组要接在它后面!
👉 所以 nxt
就是当前组反转后的尾节点
2. nxt.next = cur
- 把当前组的尾节点(
nxt
)指向cur
(下一组的开头) - 这样就完成了“当前组”和“剩余链表”的连接!
比如:
反转后:2 -> 1 (1.next 还没连)
cur 指向 3
现在让 1.next = 3
✅ 链表变成:2 -> 1 -> 3 -> 4 -> ...
3. p0.next = pre
p0
是上一组的尾节点pre
是当前组反转后的头节点(比如 2)- 所以
p0.next = pre
就是把上一组连到当前组
比如:
dummy -> 2 -> 1 -> 3 -> ...
4. p0 = nxt
- 把
p0
移动到当前组的尾节点(也就是nxt
) - 因为下一次反转后,还要把下下组接在它后面!
👉 所以 p0
始终是“上一组的尾节点”,也就是下一组的前驱
🔄 下一轮迭代
继续处理下一组 k
个节点,重复上述过程。
✅ 总结:为什么能想到 p0
?
思考过程 | 对应代码 |
---|---|
反转后,怎么把新链表接回去? | 需要一个指针记住“前驱” |
前驱是谁?是上一组的尾节点 | p0 就是这个角色 |
反转后,原来的头变成尾,要让它指向下一组 | nxt = p0.next (原头)→ nxt.next = cur |
反转后的头是 pre ,要让前驱指向它 | p0.next = pre |
更新 p0 到当前组尾,为下一组做准备 | p0 = nxt |
🎯 类比:穿珠子
想象你在穿珠子,每 k
个一组反转顺序:
p0
是你左手拿着的“最后一个穿好的珠子”- 你右手把接下来
k
个珠子反转 - 然后左手的珠子连到这组的新开头
- 最后,你把这组的最后一个珠子拿在左手里,继续下一轮
💡 小技巧记忆
记住三步:
- 保存当前组的头:
nxt = p0.next
(反转后它变成尾) - 反转 k 个节点:
pre, cur
搞定 - 连接:
nxt.next = cur
# 尾接下一组p0.next = pre
# 前驱接新头p0 = nxt
# 更新前驱为当前尾
✅ 最后:你的困惑解决了没?
你说:“我反转了 k 个节点,但不知道怎么往后推进。”
答案就是:用 p0
记住上一组的尾,每次反转完,用 p0.next = pre
接上,再把 p0
移到当前组尾(nxt
)继续。
这就是“推进”的关键!