https://blog.csdn.net/2601_95366422/article/details/159051445
上节课连接
一.题目

二.思路讲解
2.1 思路讲解
首先,题目要求我们交换链表中的相邻节点 ,比如将第1个和第2个节点交换,第3个和第4个节点交换,以此类推。这里有一个关键点:我们不能只交换节点的值 ,而必须通过改变节点的指针指向来实现真正的节点交换,因为链表的结构决定了节点本身的位置关系。
在操作链表时,为了避免处理头节点时的边界问题,我们通常需要引入一个虚拟头节点(也叫哨兵位)。这个虚拟头节点指向原链表的第一个节点,这样在交换前两个节点时,我们可以统一地使用一套逻辑,而不需要单独判断头节点的情况。
接下来,我们分析如何交换两个相邻节点。假设我们要交换节点 A 和 B ,其中 A 是 B 的前驱。那么我们需要关注四个关键节点:
-
prev :指向 A 的前一个节点(对于头节点,prev 就是虚拟头节点)。
-
cur :指向要交换的左节点 A。
-
next1 :指向要交换的右节点 B。
-
next2 :指向 B 的下一个节点,即交换后需要接上的后续部分。
有了这四个节点,我们就可以进行指针的重新连接。具体步骤是:
-
将 prev 的
next指向 B ,这样 B 成为新的左节点。 -
将 cur 的
next指向 next2 ,因为 A 要换到后面,它应该指向原来的后续部分。 -
将 B 的
next指向 cur ,这样 B 后面就接上了 A。
完成这两个节点的交换后,我们需要继续处理下一对相邻节点。此时,原来的 cur (即现在的 A )已经成为了这一对的后一个节点,而下一对要交换的是 A 后面的两个节点。因此,我们需要更新四个指针的位置:
-
新的 prev 应该指向当前这一对的后一个节点,即 cur。
-
新的 cur 应该指向 next2(即下一对的第一个节点)。
-
新的next1 应该指向cur->next
-
新的next2 应该指向next1->next
三.代码演示
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)
{
//如果没有两个节点那么无法交换
if(head == nullptr || head->next == nullptr)
return head;
ListNode* new_head = new ListNode;
new_head->next = head;//让虚拟头节点和头节点连接
ListNode* prev = new_head;//父节点
ListNode* cur = head;//要交换的左边节点
ListNode* next1 = head->next;//要交换的右边节点
ListNode* next2 = next1->next;//右边节点的下一个节点
while (cur != nullptr && next1 != nullptr)
{
//交换左右节点
prev->next = next1;//父和右连接
next1->next = cur;//右和左连接
cur->next = next2;//左和右边界的下一个节点连接
prev = cur;
cur = next2;
if(cur != nullptr)
next1 = next2->next;
if (next1 != nullptr)
next2 = next1->next;
}
return new_head->next;
}
};
四.代码讲解
一、虚拟头节点与边界判断
在链表操作中,为了统一处理头节点的特殊情况,我们首先创建一个虚拟头节点 new_head,并将其 next 指向原链表的头节点 head。这样,无论头节点是否参与交换,我们都可以用一套逻辑操作。同时,我们检查链表是否至少有两个节点:如果 head == nullptr 或 head->next == nullptr,则无法交换,直接返回原链表。
二、初始化四个关键指针
我们需要记录交换过程中涉及的四个关键节点:
-
prev:指向当前要交换的两个节点的前一个节点,初始化为虚拟头节点new_head。 -
cur:指向要交换的左节点,初始化为head(即第一个节点)。 -
next1:指向要交换的右节点,初始化为head->next(即第二个节点)。 -
next2:指向右节点的下一个节点,即交换后需要接上的后续链表,初始化为next1->next。
这四个指针的初始状态正好对应第一对节点(节点1和节点2)及其前后关系。
三、循环交换每一对节点
进入 while 循环,条件为 cur != nullptr && next1 != nullptr ,即当前还有一对节点可以交换(左节点和右节点都存在)。在循环体内,执行以下三步完成交换:
-
将
prev的next指向next1这一步让前一节点直接指向右节点,使得右节点成为新的左端。 -
将
next1->next指向cur这一步让右节点指向左节点,完成两个节点的反转。 -
将
cur->next指向next2这一步让左节点指向原来的后续链表,保证链表不断开。
经过这三步,原来的 cur(左节点)和 next1(右节点)位置互换,且链表连接正确。
四、更新指针准备下一轮交换
交换完成后,我们需要将四个指针移动到下一对节点的位置,以便继续处理。更新顺序如下:
-
prev = cur:因为cur现在已经是这一对中靠后的节点,下一对的前一个节点就是它。 -
cur = next2:next2原本是下一段的开头,现在作为下一对的左节点。 -
接着,需要重新确定
next1和next2:-
如果
cur不为空,则next1 = cur->next(即下一对的右节点)。 -
如果
next1不为空,则next2 = next1->next(即再下一个节点)。
-
注意,这里需要先判断 cur 是否为空,再取 cur->next,防止空指针访问。同样,next1 可能为空,也需要判断后赋值 next2。这种逐步更新的方式保证了指针始终有效。
五、循环结束与返回
当 cur 或 next1 为 nullptr 时,说明没有更多可交换的节点(可能是只剩一个节点或已到末尾),循环结束。最后,返回 new_head->next,即虚拟头节点的下一个节点,也就是交换后链表的新头节点。
六、关键细节
-
虚拟头节点的必要性:避免了在交换头两个节点时需要单独处理前驱为空的边界,使代码统一简洁。
-
四个指针的精确维护 :交换过程中,必须确保每一步指针的修改不会丢失后续链表的引用,特别是
next2的提前保存至关重要。 -
循环条件的动态性 :每次交换后,
cur和next1都可能变为空,因此循环条件在每次迭代前判断,确保安全。 -
指针更新的顺序 :先移动
prev和cur,再根据新的cur重新获取next1和next2,这种顺序保证了指针的连续性。