【刷题之路Ⅱ】LeetCode 24. 两两交换链表中的节点
- 一、题目描述
- 解题
- 1、方法1——递归
- 1.1、思路分析
- 1.2、代码实现
- 2、方法2——迭代
- 2.1、思路分析
- 2.2、代码实现
- 3、方法3——交替的尾插
- 3.1、思路分析
- 3.2、代码实现
一、题目描述
原题连接: 24. 两两交换链表中的节点
题目描述:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。
你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入: head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入: head = []
输出:[]
示例 3:
输入: head = [1]
输出:[1]
提示:
链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100
解题
1、方法1——递归
1.1、思路分析
想要理解递归的思路,我们可以先从链表中只有两个节点的情况入手:
当链表中只有两个节点时,进行两两交换其实很简单,只需要再创建一个新头指针newhead,然后让newhead指向原链表的第二个节点(即head->next),再将原链表的第一个节点尾插到后面即可:
但其实我们把这种方法推广到更长的链表中也是可行的,即我们每次递归都优先交换链表的前两个节点,而剩下的节点的交换我们就用递归来实现,如图:
我们先让newhead指向原链表的第二个节点,置于第二个节点后的节点的交换就交给递归来完成,完成后再让head的next指向剩下的链表,及执行head->next = swapPairs(newhead->next),最后在将第一个节点和第二个节点连接起来:
1.2、代码实现
有了以上思路,那我们写起代码来也就水到渠成了:
struct ListNode* swapPairs(struct ListNode* head){
if (NULL == head || NULL == head->next) { // 如果链表为空或只有一个节点,直接返回head即可
return head;
}
struct ListNode *newhead = head->next;
head->next = swapPairs(newhead->next);
newhead->next = head;
return newhead;
}
2、方法2——迭代
我想一定有些朋友在看了上面的递归解法后还是会一头雾水,没关系,我这里还有个相对更容易理解的思路,其实也就是在原链表上动刀子。
2.1、思路分析
我们可以先创建一个辅助节点dumbNode,初始时我们让dumbNode的next指向head:
之所以要创建这个辅助节点,其实就是为了更方便的解决链表中只有两个节点的情况,在后面我们基本就用不到dumbNode了。
同时我们还需要三个指针cur,Node1,Node2,初始时我们让cur指向dumbNode:
每一次迭代我们让Node1和Node2指向cur的后两个节点,然后将Node1和Node2的位置交换,具体交换方式如下:
然后我们再让cur向后走到原来Node2的位置,也就是交换后的Node1的位置,即cur = Node1即可完成一轮迭代。
当cur后面没有节点或者cur后面只有一个节点时,说明没有可交换的节点了,所以我们迭代停止的条件就是cur->next == NULL 或cur->next->next == NULL。
最后我们返回新的头节点,也就是dumbNode->next。
2.2、代码实现
有了以上思路,那我们写起代码来也就水到渠成了:
struct ListNode* swapPairs(struct ListNode* head){
if (NULL == head || NULL == head->next) {
return head;
}
// 创建一个辅助节点
struct ListNode dumbNode = { 0 };
dumbNode.next = head;
struct ListNode *cur = &dumbNode;
struct ListNode *Node1 = NULL;
struct ListNode *Node2 = NULL;
while (cur->next && cur->next->next) {
Node1 = cur->next;
Node2 = Node1->next;
// 两两交换
cur->next = Node2;
Node1->next = Node2->next;
Node2->next = Node1;
cur = Node1;
}
return dumbNode.next;
}
3、方法3——交替的尾插
3.1、思路分析
通过分析题目所给的示例,我们可以看出题目其实是让我们以每两个节点为一组进行位置的交换:
所以这其实也是在要求我们将两个相邻的奇偶次序的节点进行位置交换。
所以我们就可以针对这一点来设计出一个更加简单易懂的解法:
首先我们还是需要一个cur指针来帮助我们遍历链表,又因为涉及到插入新链表的操作,可能会对cur的next做更改,所以我们还需要一个next指针指向cur的下一个节点,方便插入后能找到cur的下一个节点:
同时我们还需要一个变量len来记录当前已遍历到的链表的长度,当len为奇数时,我们就将cur尾插到新链表的末尾。当len为偶数时,我们就将cur插入到新链表的倒数第一和倒数第二个节点之间。所以对于新链表我们需要两个指针pre和tail分别记录新链表的倒数第二和倒数第一个节点:
并且在所有节点都插入完后一定要记得将tail的next置为NULL,因为如果链表的长度是偶数的话,当我们把原链表的倒数第二个节点尾插到新链表中后,该节点的next其实还是指向原链表的最后一个节点的:
所以当我们把最后一个节点也插入到新链表中时,就会形成一个环:
这也正是代码会超时的原因。
3.2、代码实现
有了以上思路,那我们写起代码来也就水到渠成了:
struct ListNode* swapPairs(struct ListNode* head){
if (NULL == head) {
return NULL;
}
struct ListNode *newhead = NULL;
struct ListNode *cur = head;
struct ListNode *next = NULL; // 记录从cur的下一个节点,方便cur往后走
struct ListNode *tail = newhead; // 记录新链表的尾节点
struct ListNode *pre = newhead; // 记录新链表的尾节点的前一个节点
int len = 0; // 记录链表的长度
while (cur) {
len++;
next = cur->next;
if (len % 2 == 1) {
// 如果len是奇数,就尾插到链表的最后
if (NULL == newhead) {
newhead = cur;
tail = cur;
pre = newhead; // 更新pre
} else {
tail->next = cur;
tail = tail->next;
}
if (len >= 3) {
pre = pre->next;
}
} else {
// 如果len是偶数,那就插入到pre和tail之间
if (pre == newhead) {
cur->next = newhead;
newhead = cur;
pre = newhead; // 更新pre
} else {
cur->next = tail;
pre->next = cur;
pre = pre->next;
}
}
cur = next;
}
tail->next = NULL;
return newhead;
}