LeetCode 24 两两交换链表中的节点的 JavaScript 实现主要有迭代法和递归法两种,核心难点在于指针的正确重连顺序,避免链表断裂。以下提供两种主流解法:
解法一:迭代法(虚拟头节点 + 三指针)
引入虚拟头节点统一处理边界,每次取出一对节点进行交换。
/**
function ListNode(val, next) {
this.val = (val===undefined ? 0 : val)this.next = (next===undefined ? null : next)}
*/
var swapPairs = function(head) {
// 创建虚拟头节点,简化边界处理
const dummy = new ListNode(0, head);
let pre = dummy;// 当后面至少有两个节点时,才进行交换
while (pre.next && pre.next.next) {
const node1 = pre.next; // 第一个节点
const node2 = pre.next.next; // 第二个节点// 执行交换(注意顺序,避免链表断裂) node1.next = node2.next; // 1. node1 指向 node2 的下一个 node2.next = node1; // 2. node2 指向 node1 pre.next = node2; // 3. pre 指向 node2 // pre 前进到下一组的前驱位置(即交换后的 node1) pre = node1;}
return dummy.next;
};
执行流程(以 [1,2,3,4] 为例):
初始:dummy → 1 → 2 → 3 → 4
第一轮:pre=dummy, node1=1, node2=2
1.next = 3, 2.next = 1, dummy.next = 2
结果:dummy → 2 → 1 → 3 → 4
pre 移动到 node1(值为1)
第二轮:pre=1, node1=3, node2=4
3.next = null, 4.next = 3, 1.next = 4
结果:dummy → 2 → 1 → 4 → 3
返回 dummy.next = [2,1,4,3] ✅
解法二:递归法(代码更简洁)
递归的核心思想:交换当前两个节点,然后递归处理剩余链表。
var swapPairs = function(head) {
// 终止条件:没有节点或只有一个节点,无法交换
if (!head || !head.next) {
return head;
}
const node1 = head; const node2 = head.next; // node1 接上后续递归处理的结果 node1.next = swapPairs(node2.next); // node2 指向 node1,完成交换 node2.next = node1; // 返回交换后的头节点 return node2;};
递归展开(以 [1,2,3,4] 为例):
swapPairs(1)
node1=1, node2=2
1.next = swapPairs(3) ← 递归
node1=3, node2=4
3.next = swapPairs(null) = null
4.next = 3
return 4
1.next = 4
2.next = 1
return 2
最终:2 → 1 → 4 → 3 ✅
两种解法对比
维度 迭代法 递归法
时间复杂度 O(n) O(n)
空间复杂度 O(1) O(n)(递归栈)
代码量 稍多 更简洁
可读性 指针操作直观 逻辑清晰,符合分治思想
关键技巧总结
- 虚拟头节点:避免对头节点做特殊判断,所有节点都有统一的前驱。
- 指针重连顺序:先让 node1.next 指向 node2.next(保存后续链表),再让 node2.next 指向 node1,最后让 pre.next 指向 node2。顺序搞反会导致链表断裂。
- 递归三要素:终止条件(节点不足两个)、递归调用(处理剩余链表)、返回值(交换后的新头节点)。
两种写法都很经典,面试中建议都能写出来。迭代法空间更优,递归法代码更优雅。
需要我帮你把递归写法手动展开成迭代版本吗?面试时有时会要求"不用递归实现"。