题目描述
思路解析动画文字版
最偷懒的做法是遍历时把相邻两个节点的值对调——代码两行就过样例。但题目明说「不能只交换值」,面试官就是要看你会不会重连指针。真本事是动箭头,不是动数字。
先在头前面加一个 dummy(哨兵),让第一对也有「前驱」可接。cur 站在这一对的前面;取出这一对 a=cur.next、b=a.next。三步重连:①cur 接到 b、②a 接到 b 原来的后面、③b 回头接 a。最后 cur 跳到 a,处理下一对。下面慢放。
起点 · dummy 在头前,cur=dummy:在 1 前面加哨兵 D(dummy),这样换头时也有个固定前驱可以接。cur 从 D 出发,它后面紧跟着待交换的第一对 1、2。
取出第一对 · a=1, b=2:先看 cur 后面是不是至少还有两个。有:用 a 抓住 1、b 抓住 2。这一对就是要互换的两兄弟,先抓牢再动手。
① cur.next = b · D 改指向 2:第一步 cur.next = b。上行方块原地不动,只标断点:D 原来指 1 的那根箭头断成「·」。下行顺着新箭头把链摆顺读一遍——D 现在接到了 2(绿色那段)。1 还被 a 抓着,没丢。
② a.next = b.next · 1 接到 3:第二步 a.next = b.next。上行方块仍是 D 1 2 3 4 原地没动,只是 1→2 那根也断成「·」了。下行摆顺读出新链:1 现在接到了 3(为下一对搭好桥)。就差 2 回头接 1,这对就闭合。
③ b.next = a · 2 回头指 1:第三步 b.next = a:2 回头接 1。上行方块没动(2 仍在 1 右边),所以这根新箭头就落在 1、2 之间、箭头朝左——「1 ← 2」正是顺读「2 → 1」。看清楚:上行 2、1 两个方块一直没换位,动的全是箭头。下行把整条链摆顺读出来已是 D→2→1→3→4,第一对换好。
cur 跳到 a(1) · 准备下一对:第一对收尾后,按「顺着箭头读」的逻辑顺序把图摆正:D→2→1→3→4。再把 cur 移到 a(也就是 1)——它正好站在下一对 3、4 的前面,成了下一对的前驱。同一套三步再来一遍。
取出第二对 · a=3, b=4:cur(1) 后面还有 3、4 两个,继续。用 a 抓 3、b 抓 4。注意 4 后面是 null,待会儿 a 接过去就是结尾。
① cur.next = b · 1 改指向 4:第二对,还是那三步。第①步 cur(1).next=b(4):上行 1→3 的箭头断成「·」,节点没动。下行读出 1 现在接到了 4(绿色)。和第一对的①一模一样。
② a.next = b.next · 3 接到 null:第②步 a(3).next=b 后面。b(4)后面就是 null,所以 3 现在接到 null——它要当新的结尾了。上行 3→4 的箭头也断成「·」,节点照旧没挪。就差第③步。
③ b.next = a · 4 回头指 3:第③步 b(4).next=a(3):4 回头接 3。上行方块没动(4 仍在 3 右边),这根新箭头落在 3、4 之间、朝左——「3 ← 4」正是顺读「4 → 3」。同一套①②③第二次走完。下行把链摆顺读出 D→2→1→4→3→null。cur 再跳到 a(3),它后面是 null,凑不齐一对,循环停。
完成 · 返回 dummy.next,跳过哨兵 D:全部换完。D 这个哨兵任务结束,灰掉(虚线那个)。返回 dummy.next——也就是 ans↓ 指的 2,指针越过 D 落到 2。最终 2 → 1 → 4 → 3,和题目要的输出完全一致。返回的是 dummy.next,不是 head(1 现在排第二)。
链表换位的通用心法:换头会改第一个前驱,所以加一个 dummy 哨兵统一处理;每一对都「前驱接 b、a 接 b 后面、b 回头接 a」三步固定动作。
雷区实演 · 奇数个节点 1→2→3:奇数个时:换完 1、2 后 cur 停在 1,它后面只剩 3 一个,cur.next.next 为空→循环条件不满足,直接退出。3 保持原样接在末尾:2→1→3。这就是为什么循环要判两个 next。
面试追问:两两交换 的追问重点:替代的递归解、dummy 的作用、三步顺序的依据,把动画里的状态讲成自己的话。
「dummy + 前驱重连一段」是所有分段改链题的原子操作:本题是每段 2 个,LC25 是每段 K 个(段内先反转再把两头接回去),LC92 是只反转第 m~n 段。学会这一道,一整类链表分段题都通了——更多在 链表专题 继续,卡住就问问 AI 助教小欧。
边界三连:两两交换 的边界先看最小规模、空输入,以及奇数个节点末尾落单的情形——循环条件判两个 next 就全兜住了。
参考代码
class Solution: def swapPairs(self, head): dummy = ListNode(0, head) cur = dummy # cur 站在每一对前面 while cur.next and cur.next.next: # 后面至少还有两个 a = cur.next # 这一对的第一个 b = a.next # 这一对的第二个 cur.next = b # ① 前驱接到 b a.next = b.next # ② a 接到 b 后面 b.next = a # ③ b 回头接 a cur = a # cur 跳到 a,处理下一对 return dummy.next # 新头是 dummy.next复杂度
- 时间复杂度:O(n),每对节点只在重连时各动一次指针,n 个节点共 n/2 对 → O(n)
- 空间复杂度:O(1),只用 dummy / cur / a / b 几个指针,不随链表变长 → O(1)
可套用的代码模板
记住骨架:dummy 兜头、cur 停在段前驱、段内重连、cur 推进到新段前驱、返回 dummy.next。把「段内重连」换成不同操作,就能做两两交换、K 个一组、区间反转。右上角可切 C++ / Java。
# 凡是「按段重连链表」的题都套这个骨架dummy = ListNode(0, head)cur = dummy # cur 永远停在待处理段的前驱while 还凑得齐一段(cur): # 本题=cur.next 且 cur.next.next a = cur.next # 抓住段内要动的节点 ... # 段内重连(交换/反转/跳过) cur = 段尾 # cur 推进到新段前驱(本题=a)return dummy.next易错点
面试追问把动画讲成自己的话
追问能用递归写吗?
追问为什么必须加 dummy?
追问三步重连的顺序能调换吗?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
旋转链表
LeetCode 61 · 中等 · 沿着 链表 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题