当前位置:   article > 正文

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

链表两个结点的交换

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

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

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

1. 题目描述

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 链表中节点的数目在范围[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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 时间复杂度: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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 时间复杂度: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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 时间复杂度: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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 时间复杂度:O(n),其中n是链表的节点数量。需要对每个节点进行更新指针的操作。
  • 空间复杂度:O(n),其中n是链表的节点数量。空间复杂度主要取决于递归调用的栈空间。

5. 总结

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/数据科学灵魂/article/detail/61247
推荐阅读
相关标签
  

闽ICP备14008679号