四、24.两两交换链表中的节点
题解参考:代码随想录 (programmercarl.com)
原题链接:24. 两两交换链表中的节点 - 力扣(LeetCode)
1. 题目描述
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
![img](https://i-blog.csdnimg.cn/blog_migrate/a7dfb75daa2a8e042fda6b0d0e1ee45b.jpeg)
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
提示:
- 链表中节点的数目在范围
[0, 100]
内 0 <= Node.val <= 100
2. 解题思路
这道题目,建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。
接下来就是交换相邻两个元素了,此时一定要画图,不画图,操作多个指针很容易乱,而且要注意操作的先后顺序
初始时,cur指向虚拟头结点,然后进行如下三步:
操作之后,链表如下:
看这个可能就更直观一些了:
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
。
处理前:
处理后:
然后依次递归处理其余节点,直到遇到终止条件。
使用递归,我们需要注意的有三点:
- 返回值
- 调用单元做了什么
- 终止条件
在本题中:
- 返回值:交换完成的子链表
- 调用单元:设需要交换的两个点为
head
和newHead
,head
连接后面交换完成的子链表,newHead
连接head
,完成交换 - 终止条件:
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. 总结
这道题目一定要画图,不画图,操作多个指针很容易乱,而且要注意操作的先后顺序。