1. 题目描述
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。
你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例
输入:head = [1,2,3,4]
输出:[2,1,4,3]
解释:两两交换相邻节点,1和2交换,3和4交换
输入:head = [1,2,3,4,5]
输出:[2,1,4,3,5]
解释:两两交换,多的那个节点不动
输入:head = []
输出:[]
解释:空链表
输入:head = [1]
输出:[1]
解释:只有一个节点,无法交换
提示
链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100
2. 核心思想
关键思想:虚拟头节点 + 逐对交换
核心思路:每次找到要交换的两个节点,将它们反转,然后继续处理后面的节点。
使用虚拟头节点统一处理,因为头节点也可能被交换。
以 [1,2,3,4] 为例:
初始:dummy → 1 → 2 → 3 → 4 → nullptr
↑ ↑
node0 node1
交换 1 和 2:
步骤1:node0->next = node2(dummy指向2)
步骤2:node2->next = node1(2指向1)
步骤3:node1->next = node3(1指向3)
此时:dummy → 2 → 1 → 3 → 4 → nullptr
↑ ↑
node0 node1
移动 node0 = node1,node1 = node3,继续处理
3. 多种方法解决
方法一:四指针法(推荐)✅
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy = new ListNode(0, head); // 虚拟头节点
ListNode* node0 = dummy;
ListNode* node1 = head;
while (node1 && node1->next) {
ListNode* node2 = node1->next; // 第二个节点
ListNode* node3 = node2->next; // 下一对的头
node0->next = node2; // ① node0 指向 node2
node2->next = node1; // ② node2 指向 node1
node1->next = node3; // ③ node1 指向 node3
node0 = node1; // node0 移动到已交换部分末尾
node1 = node3; // node1 移动到下一对
}
return dummy->next;
}
};
复杂度: 时间 O(L),空间 O(1),只遍历一次 ✅
方法二:三指针法
cpp
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy = new ListNode(0, head);
ListNode* pre = dummy;
while (pre->next && pre->next->next) {
ListNode* cur = pre->next;
ListNode* nxt = cur->next;
cur->next = nxt->next; // ① cur 指向 nxt 后面
nxt->next = cur; // ② nxt 指向 cur
pre->next = nxt; // ③ pre 指向 nxt
pre = cur; // pre 移动到已交换部分末尾
}
return dummy->next;
}
};
复杂度: 时间 O(L),空间 O(1),与方法一本质相同 ✅
方法三:递归法(优雅)
cpp
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
// 递归终止条件:空或只有一个节点
if (!head || !head->next) {
return head;
}
// 保存节点
ListNode* first = head;
ListNode* second = head->next;
// 递归处理后面的链表
ListNode* remaining = swapPairs(second->next);
// 交换两个节点
first->next = remaining; // 第一个指向后面的交换结果
second->next = first; // 第二个指向第一个
return second; // 新的头节点是原来的第二个
}
};
复杂度: 时间 O(L),空间 O(L)(递归栈)✅
4. 图解过程
示例:head = [1,2,3,4]
初始状态:
dummy(0) → 1 → 2 → 3 → 4 → nullptr
↑
node0=dummy, node1=1
第1轮交换(1和2):
node2=2, node3=3
node0->next = node2 → dummy→2
node2->next = node1 → 2→1
node1->next = node3 → 1→3
更新:node0=1, node1=3
第2轮交换(3和4):
node2=4, node3=nullptr
node0->next = node2 → 1→4
node2->next = node1 → 4→3
node1->next = node3 → 3→nullptr
更新:node0=3, node1=nullptr
退出!最终:2→1→4→3→nullptr ✓
边界情况:head = [1,2,3]
dummy → 1 → 2 → 3 → nullptr
第1轮交换(1和2):
结果:dummy → 2 → 1 → 3 → nullptr
第2轮:
node0 = 1
node1 = 3
node1->next = nullptr,退出
最终:2 → 1 → 3 ✓
(多余的节点3保留)
5. 方法优缺点比较
| 方法 | 时间 | 空间 | 优点 | 缺点 |
|---|---|---|---|---|
| 四指针法 | O(L) ✅ | O(1) ✅ | 直观,命名清晰 | 代码稍长 |
| 三指针法 | O(L) ✅ | O(1) ✅ | 简洁 | pre/cur/nxt 命名需要理解 |
| 递归法 | O(L) ✅ | O(L) ❌ | 代码极简优雅 | 递归栈开销,有栈溢出风险 |
推荐方法
四指针法(方法一) 或 三指针法(方法二) 是面试和竞赛中的标准解法:
- 一趟扫描,时间最优
- O(1) 空间
- 掌握后代码很简洁
6. 完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy = new ListNode(0, head);
ListNode* node0 = dummy;
ListNode* node1 = head;
while (node1 && node1->next) {
ListNode* node2 = node1->next;
ListNode* node3 = node2->next;
node0->next = node2;
node2->next = node1;
node1->next = node3;
node0 = node1;
node1 = node3;
}
return dummy->next;
}
};
// 辅助函数:链表转 vector(方便打印)
vector<int> toVector(ListNode* head) {
vector<int> result;
while (head) {
result.push_back(head->val);
head = head->next;
}
return result;
}
// 辅助函数:vector 转链表
ListNode* toList(vector<int> vals) {
ListNode* dummy = new ListNode(0);
ListNode* curr = dummy;
for (int v : vals) {
curr->next = new ListNode(v);
curr = curr->next;
}
return dummy->next;
}
int main() {
Solution sol;
// 测试1:[1,2,3,4] → [2,1,4,3]
ListNode* h1 = toList({1, 2, 3, 4});
vector<int> r1 = toVector(sol.swapPairs(h1));
cout << "[1,2,3,4] → [";
for (int i = 0; i < r1.size(); i++)
cout << r1[i] << (i < r1.size()-1 ? "," : "");
cout << "]" << endl;
// 测试2:[1,2,3] → [2,1,3]
ListNode* h2 = toList({1, 2, 3});
vector<int> r2 = toVector(sol.swapPairs(h2));
cout << "[1,2,3] → [";
for (int i = 0; i < r2.size(); i++)
cout << r2[i] << (i < r2.size()-1 ? "," : "");
cout << "]" << endl;
// 测试3:[] → []
ListNode* h3 = toList({});
vector<int> r3 = toVector(sol.swapPairs(h3));
cout << "[] → []" << endl;
// 测试4:[1] → [1]
ListNode* h4 = toList({1});
vector<int> r4 = toVector(sol.swapPairs(h4));
cout << "[1] → [";
for (int i = 0; i < r4.size(); i++)
cout << r4[i] << (i < r4.size()-1 ? "," : "");
cout << "]" << endl;
return 0;
}
输出:
[1,2,3,4] → [2,1,4,3]
[1,2,3] → [2,1,3]
[] → []
[1] → [1]
7. 两道题对比总结
| 题目 | 核心技巧 | 关键点 |
|---|---|---|
| 删除倒数第n个 | 快慢指针差 n 步 | slow 停在目标前一个节点 |
| 两两交换 | 虚拟头 + 逐对交换 | pre/node0 停在已交换部分末尾 |
两题的共同套路:虚拟头节点统一处理头节点 + 画图理解指针变化 🔄