给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
提示:
- 链表中节点的数目在范围
[0, 100]内 0 <= Node.val <= 100
1. 算法核心思想:虚拟头节点与指针重定向
在单向链表中,要交换两个相邻节点的位置,核心操作是改变它们之间以及与前后节点之间的指针指向(next)。 由于第一对节点的交换会改变整个链表的头节点,采用虚拟头节点(Dummy Node)的设计模式。在原链表头部增加一个固定的占位节点,这样第一对节点的交换逻辑就与后续所有节点的交换逻辑完全一致,避免了针对头节点的额外 if 判断。
2. 逻辑拆解
-
前置准备: 创建虚拟节点
p并使其next指向head。使用ans保存这个虚拟节点的位置,以防在遍历过程中丢失链表的起始地址。 -
循环控制边界:
while (p->next != NULL && p->next->next != NULL)严格控制了交换的前提条件。只要后面还有完整的两个节点,就进入循环交换。这也自然处理了链表节点数为奇数或为空的边界情况(奇数时,最后一个节点落单,不满足该条件,会直接保留在原位)。 -
指针重定向(交换核心): 在每一次循环中,
p始终指向待交换数对的前驱节点。temp和temp2分别指向待交换的节点 1 和节点 2。p->next = temp2:让前驱节点越过节点 1,直接连接到节点 2。temp->next = temp2->next:将节点 1 的尾部连接到原链表节点 2 的后继部分,防止后续链表断裂丢失。temp2->next = temp:将节点 2 的指针掉头指向节点 1,完成这一对节点的反转。
-
游标后移: 交换完成后,当前的顺序变成了
p -> temp2 -> temp -> ...。此时temp变成了这一对节点中的最后一个。因此,执行p = p->next->next(实际上等同于p = temp),将游标移动到下一对需要交换的节点的前方,准备下一轮循环。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* p = new ListNode(0);
p->next = head;
ListNode* ans = p;
while (p->next!=NULL && p->next->next!=NULL) {
ListNode* temp = p->next;
ListNode* temp2 = p->next->next;
p->next = temp2;;
temp->next = temp2->next;
temp2->next = temp;
p = p->next->next;
}
return ans->next;
}
};
3. 复杂度分析
- 时间复杂度:O(N)。其中 N 为链表的节点数量。程序对链表进行了一次完整的遍历,每次循环处理两个节点,步长为 2,耗时与节点总数呈严格的线性关系。
- 空间复杂度:O(1)。算法仅在堆区申请了一个固定的虚拟头节点,并在栈区声明了有限的几个指针变量,未占用与数据规模相关的额外内存空间。
