两两交换链表中的节点 图解题解
题目不让只换数字——那真的交换两个节点,三根箭头要怎么动?
交换相邻两个链表节点,不能只改数字,必须改三根指针:前驱接到后者(cur→b)、后者接上后面那段(b→a.next 原来的位置交给 a)、后者再回头接前者(b→a)。先在链表最前面加一个哨兵节点当固定前驱,这样第一对也有地方可接,每一对的三步操作完全一致。
这道题到底在问什么
- 输入
- 1 → 2 → 3 → 4
- 输出
- 2 → 1 → 4 → 3
最优解:一步一步想明白
- 3最偷懒的做法是遍历时把相邻两个节点的值对调——代码两行就过样例。但题目明说「不能只交换值」,面试官就是要看你会不会重连指针。真本事是动箭头,不是动数字。
- 4先在头前面加一个 dummy(哨兵),让第一对也有「前驱」可接。cur 站在这一对的前面;取出这一对 a=cur.next、b=a.next。三步重连:①cur 接到 b、②a 接到 b 原来的后面、③b 回头接 a。最后 cur 跳到 a,处理下一对。下面慢放。
- 5cur=dummy(哨兵)在 1 前面加哨兵 D(dummy),这样换头时也有个固定前驱可以接。cur 从 D 出发,它后面紧跟着待交换的第一对 1、2。
- 6a=cur.next=1 b=a.next=2先看 cur 后面是不是至少还有两个。有:用 a 抓住 1、b 抓住 2。这一对就是要互换的两兄弟,先抓牢再动手。
- 7D.next = b(2) · 节点没挪,只改 D 这根箭头第一步 cur.next = b。上行方块原地不动,只标断点:D 原来指 1 的那根箭头断成「·」。下行顺着新箭头把链摆顺读一遍——D 现在接到了 2(绿色那段)。1 还被 a 抓着,没丢。
- 8a.next = b原来的后面(3) · 还是只改箭头第二步 a.next = b.next。上行方块仍是 D 1 2 3 4 原地没动,只是 1→2 那根也断成「·」了。下行摆顺读出新链:1 现在接到了 3(为下一对搭好桥)。就差 2 回头接 1,这对就闭合。
- 9b.next = a(1) → 第一对换好(节点始终没挪)第三步 b.next = a:2 回头接 1。上行方块没动(2 仍在 1 右边),所以这根新箭头就落在 1、2 之间、箭头朝左——「1 ← 2」正是顺读「2 → 1」。看清楚:上行 2、1 两个方块一直没换位,动的全是箭头。下行把整条链摆顺读出来已是 D→2→1→3→4,第一对换好。
- 10cur = a(1)第一对收尾后,按「顺着箭头读」的逻辑顺序把图摆正:D→2→1→3→4。再把 cur 移到 a(也就是 1)——它正好站在下一对 3、4 的前面,成了下一对的前驱。同一套三步再来一遍。
- 11a=cur.next=3 b=a.next=4cur(1) 后面还有 3、4 两个,继续。用 a 抓 3、b 抓 4。注意 4 后面是 null,待会儿 a 接过去就是结尾。
- 12cur(1).next = b(4) · 同一套三步,第①步第二对,还是那三步。第①步 cur(1).next=b(4):上行 1→3 的箭头断成「·」,节点没动。下行读出 1 现在接到了 4(绿色)。和第一对的①一模一样。
- 13a(3).next = b后面(null) · 第②步第②步 a(3).next=b 后面。b(4)后面就是 null,所以 3 现在接到 null——它要当新的结尾了。上行 3→4 的箭头也断成「·」,节点照旧没挪。就差第③步。
- 14b(4).next = a(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,凑不齐一对,循环停。
- 15答案 = dummy.next = 2 → 1 → 4 → 3全部换完。D 这个哨兵任务结束,灰掉(虚线那个)。返回 dummy.next——也就是 ans↓ 指的 2,指针越过 D 落到 2。最终 2 → 1 → 4 → 3,和题目要的输出完全一致。返回的是 dummy.next,不是 head(1 现在排第二)。
- 18链表换位的通用心法:换头会改第一个前驱,所以加一个 dummy 哨兵统一处理;每一对都「前驱接 b、a 接 b 后面、b 回头接 a」三步固定动作。
- 20换完第一对,cur 停在 1,3 落单原样留着奇数个时:换完 1、2 后 cur 停在 1,它后面只剩 3 一个,cur.next.next 为空→循环条件不满足,直接退出。3 保持原样接在末尾:2→1→3。这就是为什么循环要判两个 next。
- 24「dummy + 前驱重连一段」是所有分段改链题的原子操作:本题是每段 2 个,LC25 是每段 K 个(段内先反转再把两头接回去),LC92 是只反转第 m~n 段。学会这一道,一整类链表分段题都通了——更多在 链表专题 继续,卡住就问问 AI 助教小欧。
⚠️ 容易写错的地方
✗ 错:直接交换两个节点的 val
✓ 对:重连 cur.next / a.next / b.next 三根指针
题目明令禁止只换值,面试就是看你会不会动指针
✗ 错:三步顺序乱写、先动 a.next
✓ 对:先 cur.next=b,再 a.next=b.next,最后 b.next=a
顺序错会让某根指针提前丢失后继,链就断了
✗ 错:循环只判 cur.next
✓ 对:必须 cur.next 且 cur.next.next 都在
剩一个落单节点时不能换,否则 a.next 会空指针
完整代码(Python / C++ / Java)
Python
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.nextC++
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode dummy(0, head);
ListNode* cur = &dummy;
while (cur->next && cur->next->next) {
ListNode* a = cur->next; // 第一个
ListNode* b = a->next; // 第二个
cur->next = b; // ① 前驱接 b
a->next = b->next; // ② a 接 b 后面
b->next = a; // ③ b 回头接 a
cur = a; // 跳到 a
}
return dummy.next;
}
};Java
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(0, head);
ListNode cur = dummy;
while (cur.next != null && cur.next.next != null) {
ListNode a = cur.next; // 第一个
ListNode b = a.next; // 第二个
cur.next = b; // ① 前驱接 b
a.next = b.next; // ② a 接 b 后面
b.next = a; // ③ b 回头接 a
cur = a; // 跳到 a
}
return dummy.next;
}
}套路模板 · dummy + 前驱重连一段(背骨架)
# 凡是「按段重连链表」的题都套这个骨架
dummy = ListNode(0, head)
cur = dummy # cur 永远停在待处理段的前驱
while 还凑得齐一段(cur): # 本题=cur.next 且 cur.next.next
a = cur.next # 抓住段内要动的节点
... # 段内重连(交换/反转/跳过)
cur = 段尾 # cur 推进到新段前驱(本题=a)
return dummy.nextListNode dummy(0, head);
ListNode* cur = &dummy; // 停在待处理段前驱
while (/* 还凑得齐一段 */) {
ListNode* a = cur->next; // 抓段内节点
/* 段内重连:交换/反转/跳过 */
cur = /* 段尾,本题=a */; // 推进
}
return dummy.next;ListNode dummy = new ListNode(0, head);
ListNode cur = dummy; // 停在待处理段前驱
while (/* 还凑得齐一段 */) {
ListNode a = cur.next; // 抓段内节点
/* 段内重连:交换/反转/跳过 */
cur = /* 段尾,本题=a */; // 推进
}
return dummy.next;复杂度
时间复杂度
O(n)
每对节点只在重连时各动一次指针,n 个节点共 n/2 对 → O(n)
空间复杂度
O(1)
只用 dummy / cur / a / b 几个指针,不随链表变长 → O(1)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 两两交换链表中的节点 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
能用递归写吗?+
能。递归:先换好头两个,head.next 接到「后面递归换完的结果」,再让第二个指回 head,返回第二个。代码短,但递归栈是 O(n) 空间,不如迭代 O(1)。
为什么必须加 dummy?+
因为交换第一对会改变头节点。dummy 当固定前驱,第一对和后续对用同一套逻辑,最后返回 dummy.next,省掉换头特判。
三步重连的顺序能调换吗?+
不能随便调。核心是动某根指针前,它原来指的后继必须已经被别人接走。安全序:cur.next=b → a.next=b.next → b.next=a。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。