两两交换链表中的节点 图解题解
题目不让只换数字——那真的交换两个节点,三根箭头要怎么动?
交换相邻两个链表节点,不能只改数字,必须改三根指针:前驱接到后者(cur→b)、后者接上后面那段(b→a.next 原来的位置交给 a)、后者再回头接前者(b→a)。先在链表最前面加一个哨兵节点当固定前驱,这样第一对也有地方可接,每一对的三步操作完全一致。
这道题到底在问什么
- 输入
- 1→2→3→4
- 输出
- 2→1→4→3
最优解:一步一步想明白
- 3哑结点让「换第一组」和后面各组写法完全一致,不必为头节点单独写特例。下面每一组都在套这三步。
- 4哑结点 D 接在头节点 1 前面,pre 指向 D。只要 pre 后面还有「两个」节点,就交换这一组。
- 5第 1 组:pre=D,first=pre.next=1,second=first.next=2。目标——让 2 排到 1 前面,即这一组变成 2→1。
- 6第一根:first.next = second.next,让 1 先接住「下一组」的 3(nxt 指着它)。先记住后面,等会儿断开也不丢链。
- 7第二根:second.next = first,让 2 指回 1(图上这一对的箭头掉成 ←)。现在这一组内部已经是 2→1 的顺序。
- 8第三根:pre.next = second,让前驱 D 接到后一个 2——这一组彻底接好(变绿)。然后 pre 跳到 1(它现在是这一组的尾),作为下一组的前驱。
- 9第 2 组:pre=2,first=pre.next=3,second=first.next=4。目标——让 4 排到 3 前面,即这一组变成 4→3。
- 10第一根:first.next = second.next,让 3 先接住「下一组」的 5(nxt 指着它)。先记住后面,等会儿断开也不丢链。
- 11第二根:second.next = first,让 4 指回 3(图上这一对的箭头掉成 ←)。现在这一组内部已经是 4→3 的顺序。
- 12第三根:pre.next = second,让前驱 2 接到后一个 4——这一组彻底接好(变绿)。然后 pre 跳到 3(它现在是这一组的尾),作为下一组的前驱。
- 13第 3 组:pre=4,first=pre.next=5,second=first.next=6。目标——让 6 排到 5 前面,即这一组变成 6→5。
- 14第一根:first.next = second.next,让 5 先接住「下一组」的 7(nxt 指着它)。先记住后面,等会儿断开也不丢链。
- 15第二根:second.next = first,让 6 指回 5(图上这一对的箭头掉成 ←)。现在这一组内部已经是 6→5 的顺序。
- 16第三根:pre.next = second,让前驱 4 接到后一个 6——这一组彻底接好(变绿)。然后 pre 跳到 5(它现在是这一组的尾),作为下一组的前驱。
- 17第 4 组:pre=6,first=pre.next=7,second=first.next=8。目标——让 8 排到 7 前面,即这一组变成 8→7。
- 18第一根:first.next = second.next,这里 second 后面是 null,所以 7 接向 null(它将成为整条链的表尾)。
- 19第二根:second.next = first,让 8 指回 7(图上这一对的箭头掉成 ←)。现在这一组内部已经是 8→7 的顺序。
- 20第三根:pre.next = second,让前驱 6 接到后一个 8——这一组彻底接好(变绿)。然后 pre 跳到 7;后面不足两个节点,循环结束。
- 21四组都交换完,pre 走到尾后 pre.next 不足两个、循环结束。返回 dummy.next,新头是 2。整条变成 2→1→4→3→6→5→8→7。
⚠️ 容易写错的地方
✗ 错:没加哑结点,单独处理头节点
✓ 对:加哑结点 D,pre 从 D 起,第一组和后面写法一致
头节点会被换掉,没哑结点要为它写特例,易错
✗ 错:三根指针顺序乱改
✓ 对:先 first.next=second.next,再 second.next=first,最后 pre.next=second
先记住「下一组」,否则改了 second.next 后就找不到后面
✗ 错:pre 推进到 second
✓ 对:pre 应推进到 first
交换后 first 才是这一组的尾节点,下一组的前驱是它
完整代码(Python / C++ / Java)
Python
def swapPairs(head):
dummy = ListNode(0, head)
pre = dummy
while pre.next and pre.next.next:
first = pre.next
second = first.next
first.next = second.next # ① 前者接下一组
second.next = first # ② 后者指回前者
pre.next = second # ③ 前驱接后者
pre = first # 前者成新尾,做下一组前驱
return dummy.nextC++
ListNode* swapPairs(ListNode* head){
ListNode dummy(0); dummy.next = head;
ListNode* pre = &dummy;
while(pre->next && pre->next->next){
ListNode* first = pre->next;
ListNode* second = first->next;
first->next = second->next; // ①
second->next = first; // ②
pre->next = second; // ③
pre = first;
}
return dummy.next;
}Java
public ListNode swapPairs(ListNode head){
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;
while (pre.next != null && pre.next.next != null) {
ListNode first = pre.next;
ListNode second = first.next;
first.next = second.next; // ① 前者接下一组
second.next = first; // ② 后者指回前者
pre.next = second; // ③ 前驱接后者
pre = first;
}
return dummy.next;复杂度
时间
O(n)
每个节点只处理一次,逐对推进
空间
O(1)
只用 dummy/pre/first/second 几个指针,原地交换
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 两两交换链表中的节点 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
能不能只交换节点里的值,不改指针?+
本题通常要求真正交换节点。若数据是大对象、或面试明确禁止换值,就必须改指针;改指针也是考察点。
递归怎么写?+
每次交换前两个:newHead=second;first.next=swapPairs(second.next);second.next=first;返回 second。空间 O(n) 递归栈,不如迭代 O(1)。
推广到「每 k 个一组翻转」(LC25) 怎么变?+
把「取 2 个交换」换成「取 k 个做局部反转」,前驱接反转后的头、原组头接下一组——同样靠哑结点 + 三指针接缝。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 两两交换链表中的节点 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。