代码随想录刷题-链表-两两交换链表中的节点

两两交换链表中的节点

本节对应代码随想录中:代码随想录,讲解视频:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点_哔哩哔哩_bilibili

习题

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

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。


输入:head = [1,2,3,4]
输出:[2,1,4,3]

我的解法

由于上面的反转链表用了双指针,这题我首先想到的是用双指针的解法。定义一个虚拟头节点。

对于0 1 2 3 4,cur 指针指向1也就是待交换的两个节点中的第一个节点,由于交换后1前面的0的 next 也会改变,所以我的想法是定义第二个指针 pre 用来记录 cur 前面的节点。

定义一个临时指针等于 cur 的 next 即2,然后就是1.next=3、2.next=1、0.next=2,如果不用临时指针保存2,那么1.next=3时就无法找到2了。

交换后顺序为0 2 1 3 4,此时 pre 还是指向0,cur 指向2。cur=cur->next 即可让 cur 遍历到下一个待交换元素的第一个位置,而在这之前首先让 pre=cur 先让 pre 前进到下一个待交换元素的前一个。

而 while 循环的终止条件,每次 cur 都会指向的是待交换元素的第一个。如果是偶数个那么 cur 为 nullptr,如果是奇数个,那么 cur->next 为 nullptr。

一顿 next 操作后很可能就会有点迷,只要记住 cur 和 pre 这些都是指针,它们记录的是地址,不管你再怎么 next 操作,地址中的值都没发生变化。也就是说,刚开始0 1 2 3 4,cur 指向的是1,交换操作后变成0 2 1 3 4,此时 cur 指向的还是1。因此如果想让 cur 指向3只需让 cur=cur->next 即可。

class Solution {
   public:
    ListNode* swapPairs(ListNode* head) {
        
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;

        ListNode* pre = dummyHead;//0
        ListNode* cur = head; //1
        // 0 1 2 3
        while (cur != nullptr && cur->next != nullptr) {
            ListNode* next_tmp = cur->next;//2
            cur->next = next_tmp->next;//1.next=3
            next_tmp->next = cur;//2.next=1
            pre->next = next_tmp;//0.next=2

            pre = cur;
            cur = cur->next;
        }

        return dummyHead->next;
    }
};
  • 时间复杂度:O(n)。其中n为链表的长度。因为算法需要遍历整个链表,时间复杂度与链表长度成正比。
  • 空间复杂度:O(1)。因为算法只使用了常数级别的额外空间,即三个指针变量和一个虚拟头节点。无论输入的链表有多长,算法所需的额外空间都是固定的,与链表长度无关。

代码随想录解法

在我的解法中,使用了双指针和一个临时指针变量。而在代码随想录中,作者使用了一个指针,两个临时指针变量。

Carl 的解法中 cur 指向的是待交换节点的前一个节点,交换顺序如下图所示。执行步骤一后,节点1就没法找到,所以用一个临时节点记录节点1。执行步骤2后,节点3就没法找到,所以再用一个临时变量记录节点3。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WNQIxCg5-1679135679524)(https://code-thinking.cdn.bcebos.com/pics/24.%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B91.png)]

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
        dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
        ListNode* cur = dummyHead;
        while(cur->next != nullptr && cur->next->next != nullptr) {
            ListNode* tmp = cur->next; // 记录临时节点 1
            ListNode* tmp1 = cur->next->next->next; // 记录临时节点 3

            cur->next = cur->next->next;    // 步骤一 0.next=2
            cur->next->next = tmp;          // 步骤二 2.next=1
            cur->next->next->next = tmp1;   // 步骤三 1.next=3

            cur = cur->next->next; // cur移动两位,准备下一轮交换
        }
        return dummyHead->next;
    }
};
  • 时间复杂度:O(n)。其中n为链表的长度。因为算法需要遍历整个链表,时间复杂度与链表长度成正比。
  • 空间复杂度:O(1)。因为算法只使用了常数级别的额外空间,即四个指针变量和一个虚拟头节点。无论输入的链表有多长,算法所需的额外空间都是固定的,与链表长度无关。

但其实这里只用一个指针就可以,还是上面的例子,0 1 2 3 4。Carl 的解法中 cur 指向0,定义了两个临时变量分别保存1和3,但其实只需要一个临时变量保存2即可。

为什么保存2可以只用一个临时变量呢?我们知道转换后是0 2 1 3,再看三个步骤的顺序。

1.next=3、2.next=1、0.next=2,刚好是转换后的顺序从后往前,而 Carl 的顺序是从前往后。由于单链表只有向后 next 的顺序,从前往后的话,前面的 next 值都变了,就会失去更多原有可以根据 next 找到的值。执行1.next=3后,由于原本2可以通过1.next 找到,现在1.next=3了,所以就需要一个临时变量来保存2。

// cur:0
// 0 1 2 3 4
ListNode* tmp = cur->next->next;  // 记录临时节点2
cur->next->next = tmp->next;      // 步骤一 1.next=3
tmp->next = cur->next;            // 步骤二 2.next=1
cur->next = tmp;                  // 步骤三 0.next=2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值