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

两两交换链表中的节点

本节对应代码随想录中:代码随想录,讲解视频:帮你把链表细节学清楚! | 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是交换链表节点的C语言代码: ```c #include <stdio.h> #include <stdlib.h> // 定义链表节点结构体 typedef struct Node { int data; struct Node* next; } Node; // 定义交换链表节点的函数 void swapNodes(Node** head, int x, int y) { // 如果x和y相同,直接返回 if (x == y) { return; } // 定义需要交换节点和它们的前驱节点 Node *prevX = NULL, *currX = *head; Node *prevY = NULL, *currY = *head; // 寻找需要交换节点和它们的前驱节点 while (currX != NULL && currX->data != x) { prevX = currX; currX = currX->next; } while (currY != NULL && currY->data != y) { prevY = currY; currY = currY->next; } // 如果x或y不存在于链表,直接返回 if (currX == NULL || currY == NULL) { return; } // 如果x不是头节点,更新前驱节点的next指针 if (prevX != NULL) { prevX->next = currY; } else { // 如果x是头节点,更新头指针 *head = currY; } // 如果y不是头节点,更新前驱节点的next指针 if (prevY != NULL) { prevY->next = currX; } else { // 如果y是头节点,更新头指针 *head = currX; } // 交换节点的next指针 Node* temp = currY->next; currY->next = currX->next; currX->next = temp; } // 定义输出链表的函数 void printList(Node* node) { while (node != NULL) { printf("%d ", node->data); node = node->next; } } int main() { // 创建一个测试用例链表 Node* head = (Node*)malloc(sizeof(Node)); head->data = 1; head->next = (Node*)malloc(sizeof(Node)); head->next->data = 2; head->next->next = (Node*)malloc(sizeof(Node)); head->next->next->data = 3; head->next->next->next = (Node*)malloc(sizeof(Node)); head->next->next->next->data = 4; head->next->next->next->next = NULL; // 输出原始链表 printf("原始链表: "); printList(head); printf("\n"); // 交换节点 swapNodes(&head, 2, 4); // 输出交换后的链表 printf("交换后的链表: "); printList(head); printf("\n"); return 0; } ``` 以上代码将创建一个测试用例链表,输出原始链表交换节点,输出交换后的链表。可以根据自己的需要修改测试用例链表

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值