链表篇:24.两两交换链表中的节点

四、24.两两交换链表中的节点

题解参考:代码随想录 (programmercarl.com)

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

1. 题目描述

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

示例 1:

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

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

提示:

  • 链表中节点的数目在范围[0, 100]
  • 0 <= Node.val <= 100

2. 解题思路

这道题目,建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。

接下来就是交换相邻两个元素了,此时一定要画图,不画图,操作多个指针很容易乱,而且要注意操作的先后顺序

初始时,cur指向虚拟头结点,然后进行如下三步:

image-20230501181224904

操作之后,链表如下:

image-20230501181346575

看这个可能就更直观一些了:

image-20230501181644236

3. 迭代法

这个版本就是通过while循环实现上述交换过程。注意这里的循环终止条件

  • 当链表节点数为偶数时:cur->next 为空,退出循环
  • 当链表节点数为奇数时,cur->next->next 为空,退出循环
  • 即终止条件为while(cur->next && cur->next->next),注意:cur->next一定要写在前面,不然可能会出现空指针异常,因为如果为偶数的时候,cur->next已经为空,再去取cur->next的next肯定会报错

代码如下所示

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

// 迭代版本
struct ListNode* swapPairs(struct ListNode* head){
    // 起别名
    typedef struct ListNode ListNode; 
    // 开辟虚拟头节点
    ListNode* dummyHead = (ListNode*)malloc(sizeof(ListNode));
    dummyHead->next = head;
    // 当前节点
    ListNode* cur = dummyHead;
    // 当链表节点数为偶数时:cur->next 为空,退出循环
    // 当链表节点数为奇数时,cur->next->next 为空,退出循环
    while(cur->next && cur->next->next) {
        // 临时节点1,第一次保存1
        ListNode* tmp1 = cur->next;
        // 临时节点2,第一次保存3
        ListNode* tmp2 = cur->next->next->next;
        // 交换节点
        cur->next = cur->next->next;
        cur->next->next = tmp1;
        cur->next->next->next = tmp2;
        // cur后移两位
        cur = cur->next->next; 
    }
    return dummyHead->next;
}
  • 时间复杂度:O(n),其中*n*是链表的节点数量。需要对每个节点进行更新指针的操作。
  • 空间复杂度:O(1)

4. 递归版本

递归版本的实现逻辑和迭代是一样的,只不过使用递归的方式代替了循环。

递归的终止条件是链表中没有节点,或者链表中只有一个节点,此时无法进行交换。

如果链表中至少有两个节点,则在两两交换链表中的节点之后,原始链表的头节点变成新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。链表中的其余节点的两两交换可以递归地实现。在对链表中的其余节点递归地两两交换之后,更新节点之间的指针关系,即可完成整个链表的两两交换。

head表示原始链表的头节点,新的链表的第二个节点,用newHead表示新的链表的头节点,原始链表的第二个节点,则原始链表中的其余节点的头节点是 newHead.next。令head.next = swapPairs(newHead.next),表示将其余节点进行两两交换,交换后的新的头节点为head的下一个节点。然后令 newHead.next = head,即完成了所有节点的交换。最后返回新的链表的头节点newHead

处理前:

image-20230501184426551

处理后:

image-20230501184508294

然后依次递归处理其余节点,直到遇到终止条件。

使用递归,我们需要注意的有三点:

  1. 返回值
  2. 调用单元做了什么
  3. 终止条件

在本题中:

  1. 返回值:交换完成的子链表
  2. 调用单元:设需要交换的两个点为headnewHeadhead连接后面交换完成的子链表,newHead连接head,完成交换
  3. 终止条件:head为空指针或者newHead为空指针,也就是当前无节点或者只有一个节点,无法进行交换

C语言递归版本一:先递归,后交换,这个版本和上述的分析流程一致

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

// 递归版本1
struct ListNode* swapPairs(struct ListNode* head){
    // 起别名
    typedef struct ListNode ListNode; 
    // 递归终止条件,链表中没有节点,或者链表中只有一个节点,此时无法进行交换。
    if(head == NULL || head->next == NULL) {
        return head;
    }
    // 原始链表的第二个节点,新链表的头节点
    ListNode* newHead = head->next; 
    // newHead->next指向的是后面未处理的链表
    // 第一次处理完后变成2->1->3->4,newHead->next指向的是3
    head->next = swapPairs(newHead->next);
    // 将新的头节点的next指针指向老的头节点
    newHead->next = head;
    return newHead;
}
  • 时间复杂度:O(n),其中*n*是链表的节点数量。需要对每个节点进行更新指针的操作。
  • 空间复杂度:O(n),其中*n*是链表的节点数量。空间复杂度主要取决于递归调用的栈空间。

C语言递归版本二:先交换,后递归。这个版本和上面的略有不同,但是更便于理解

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

// 递归版本2
struct ListNode* swapPairs(struct ListNode* head){
    // 起别名
    typedef struct ListNode ListNode; 
    // 递归终止条件,链表中没有节点,或者链表中只有一个节点,此时无法进行交换。
    if(head == NULL || head->next == NULL) {
        return head;
    }
    // 原始链表的第二个节点,新链表的头节点
    ListNode* newHead = head->next; 
    // 交换节点
    head->next = newHead->next;
    newHead->next = head;
    // 此时head->next指向的是下一次要处理的链表头节点
    // 第一次处理完之后为2->1->3->4,此时head->next指向的是3
    head->next = swapPairs(head->next);
    return newHead;
}
  • 时间复杂度:O(n),其中*n*是链表的节点数量。需要对每个节点进行更新指针的操作。
  • 空间复杂度:O(n),其中*n*是链表的节点数量。空间复杂度主要取决于递归调用的栈空间。

Java递归版本二:先交换,后递归

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
 
// 递归版本2:Java版,先交换,后递归
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null) {
            return head;
        }
        ListNode newHead = head.next;
        head.next = newHead.next;
        newHead.next = head;
        head.next = swapPairs(head.next);
        return newHead;
    }
}
  • 时间复杂度:O(n),其中n是链表的节点数量。需要对每个节点进行更新指针的操作。
  • 空间复杂度:O(n),其中n是链表的节点数量。空间复杂度主要取决于递归调用的栈空间。

5. 总结

这道题目一定要画图,不画图,操作多个指针很容易乱,而且要注意操作的先后顺序。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值