0024.两两交换链表中的节点
难度:🟡 中等
标签:链表
、递归
题目描述
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
解题思路
核心思想
本题的核心是在原地两两交换链表中的相邻节点。要交换一对节点 node1
和 node2
,我们不仅需要改变它们自身的 next
指针,还需要改变 node1
的前驱节点的 next
指针,使其指向交换后的 node2
。
思路选择
迭代法 + 虚拟头节点(Dummy Head) 是解决此问题的标准最优思路。
虚拟头节点:我们创建一个
dummy
节点,其next
指向原始的head
。这可以极大地简化边界情况的处理,尤其是交换头两个节点时,我们无需编写特殊逻辑。指针迭代:我们使用一个指针
prev
,它始终指向 待交换的一对节点的前一个节点。通过这个prev
指针,我们可以方便地进行交换操作。
关键步骤
创建虚拟头节点:
const dummyHead = new ListNode(0, head)
。初始化指针:创建一个指针
prev = dummyHead
。循环遍历:当
prev
后面至少还有两个节点时(即prev.next
和prev.next.next
都不为空),持续循环。定义待交换节点: a.
node1 = prev.next
b.node2 = prev.next.next
执行交换: a.
prev.next = node2
(前驱节点指向第二个节点) b.node1.next = node2.next
(第一个节点指向第三个节点) c.node2.next = node1
(第二个节点指向第一个节点)移动
prev
指针:完成交换后,node1
已经成为了这对节点的第二个。我们将prev
移动到node1
的位置,为下一轮交换做准备。返回结果:循环结束后,返回
dummyHead.next
,它指向新链表的头节点。
代码实现
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var swapPairs = function(head) {
const dummyHead = new ListNode(0, head);
let prev = dummyHead;
while (prev.next !== null && prev.next.next !== null) {
const node1 = prev.next;
const node2 = prev.next.next;
// 执行交换
prev.next = node2;
node1.next = node2.next;
node2.next = node1;
// 移动 prev 指针到下一对节点的前面
prev = node1;
}
return dummyHead.next;
};
复杂度分析
时间复杂度:O(n) 其中 n 是链表的长度。我们只需要对链表进行一次完整的遍历。
空间复杂度:O(1) 我们只使用了常数个额外变量作为指针,操作是在原链表上进行的,空间消耗是恒定的。
相关题目
总结
本题是链表指针操作的经典练习题。其核心在于通过一个虚拟头节点和一个前驱指针,来清晰、安全地完成节点的重新链接。理解指针在交换过程中的变化是掌握此题的关键。