链表(两两交换链表中的节点)(2)

https://blog.csdn.net/2601_95366422/article/details/159051445

上节课连接

一.题目

24. 两两交换链表中的节点 - 力扣(LeetCode)

二.思路讲解

2.1 思路讲解

首先,题目要求我们交换链表中的相邻节点 ,比如将第1个和第2个节点交换,第3个和第4个节点交换,以此类推。这里有一个关键点:我们不能只交换节点的值 ,而必须通过改变节点的指针指向来实现真正的节点交换,因为链表的结构决定了节点本身的位置关系。

在操作链表时,为了避免处理头节点时的边界问题,我们通常需要引入一个虚拟头节点(也叫哨兵位)。这个虚拟头节点指向原链表的第一个节点,这样在交换前两个节点时,我们可以统一地使用一套逻辑,而不需要单独判断头节点的情况。

接下来,我们分析如何交换两个相邻节点。假设我们要交换节点 AB ,其中 AB 的前驱。那么我们需要关注四个关键节点:

  • prev :指向 A 的前一个节点(对于头节点,prev 就是虚拟头节点)。

  • cur :指向要交换的左节点 A

  • next1 :指向要交换的右节点 B

  • next2 :指向 B 的下一个节点,即交换后需要接上的后续部分。

有了这四个节点,我们就可以进行指针的重新连接。具体步骤是:

  1. prevnext 指向 B ,这样 B 成为新的左节点。

  2. curnext 指向 next2 ,因为 A 要换到后面,它应该指向原来的后续部分。

  3. Bnext 指向 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 == nullptrhead->next == nullptr,则无法交换,直接返回原链表。

二、初始化四个关键指针

我们需要记录交换过程中涉及的四个关键节点:

  • prev :指向当前要交换的两个节点的前一个节点,初始化为虚拟头节点 new_head

  • cur :指向要交换的左节点,初始化为 head(即第一个节点)。

  • next1 :指向要交换的右节点,初始化为 head->next(即第二个节点)。

  • next2 :指向右节点的下一个节点,即交换后需要接上的后续链表,初始化为 next1->next

这四个指针的初始状态正好对应第一对节点(节点1和节点2)及其前后关系。

三、循环交换每一对节点

进入 while 循环,条件为 cur != nullptr && next1 != nullptr ,即当前还有一对节点可以交换(左节点和右节点都存在)。在循环体内,执行以下三步完成交换:

  1. prevnext 指向 next1 这一步让前一节点直接指向右节点,使得右节点成为新的左端。

  2. next1->next 指向 cur 这一步让右节点指向左节点,完成两个节点的反转。

  3. cur->next 指向 next2 这一步让左节点指向原来的后续链表,保证链表不断开。

经过这三步,原来的 cur(左节点)和 next1(右节点)位置互换,且链表连接正确。

四、更新指针准备下一轮交换

交换完成后,我们需要将四个指针移动到下一对节点的位置,以便继续处理。更新顺序如下:

  • prev = cur :因为 cur 现在已经是这一对中靠后的节点,下一对的前一个节点就是它。

  • cur = next2next2 原本是下一段的开头,现在作为下一对的左节点。

  • 接着,需要重新确定 next1next2

    • 如果 cur 不为空,则 next1 = cur->next(即下一对的右节点)。

    • 如果 next1 不为空,则 next2 = next1->next(即再下一个节点)。

注意,这里需要先判断 cur 是否为空,再取 cur->next,防止空指针访问。同样,next1 可能为空,也需要判断后赋值 next2。这种逐步更新的方式保证了指针始终有效。

五、循环结束与返回

curnext1nullptr 时,说明没有更多可交换的节点(可能是只剩一个节点或已到末尾),循环结束。最后,返回 new_head->next,即虚拟头节点的下一个节点,也就是交换后链表的新头节点。

六、关键细节
  • 虚拟头节点的必要性:避免了在交换头两个节点时需要单独处理前驱为空的边界,使代码统一简洁。

  • 四个指针的精确维护 :交换过程中,必须确保每一步指针的修改不会丢失后续链表的引用,特别是 next2 的提前保存至关重要。

  • 循环条件的动态性 :每次交换后,curnext1 都可能变为空,因此循环条件在每次迭代前判断,确保安全。

  • 指针更新的顺序 :先移动 prevcur,再根据新的 cur 重新获取 next1next2,这种顺序保证了指针的连续性。

相关推荐
卖男孩的小火柴.1 小时前
java内置方法总结及基础算法
java·算法
XWalnut1 小时前
LeetCode刷题 day8
算法·leetcode·职场和发展
知识分享小能手2 小时前
MongoDB入门学习教程,从入门到精通,MongoDB的分片管理(17)
数据库·学习·mongodb
Reuuse2 小时前
基于 C++ 的网页五子棋对战项目实战
开发语言·c++
世人万千丶2 小时前
Flutter 框架跨平台鸿蒙开发 - 嫉妒分析器应用
学习·flutter·华为·开源·harmonyos·鸿蒙
REDcker2 小时前
Android Bionic Libc 原理与实现综述
android·c++·c·ndk·native·bionic
我能坚持多久2 小时前
利用Date类的实现对知识巩固与自省
开发语言·c++
-SGlow-2 小时前
Linux相关概念和易错知识点(51)(mmap文件映射、共享内存原理、malloc的原理)
linux·c语言·算法·内核