24. 两两交换链表中的节点
题目
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
解题思路
- 只要是涉及节点指向的操作,就一定要记得使用虚拟头节点,虚拟头节点的作用主要是为了使头节点的操作和后面节点的操作保持一致。不然,每次操作都要单独操作头结点,很麻烦。
- 要操作第1、2各节点,那cur就一定要放在第1个节点前,操作第3个节点,cur就指向第2个节点。总之要一直保持在需要操作节点之前。
- 额外补充一点:第1个节点就是头节点。
如下是代码随想录的画图解题思路,这里可能丢失的节点是1和3,因此需要先用临时变量进行保存:
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode *fakeNode = new ListNode(0);
fakeNode->next = head;
ListNode *cur = fakeNode;
//必须先判断 cur->next 是否为空,否则如果先判断 cur->next->next,如果cur->next为空则会报错
while (cur->next != nullptr && cur->next->next != nullptr) {
// 先把cur之后的两个节点保存下来在临时变量中,以免丢失节点。
ListNode *temp1 = cur->next;
ListNode *temp2 = cur->next->next->next;
// 步骤一:
// 如果不用temp保存第1个节点,那此时赋值后,节点1就找不到了。
// 原来是 cur->next ,现在 cur->next 已经指向了节点2
cur->next = cur->next->next;
//步骤二:
//此时节点2是cur->next
cur->next->next = temp1;
//步骤三:
temp1->next = temp2;
//移动cur节点,进行下一次循环
cur = cur->next->next;
}
return fakeNode->next;
}
};
19.删除链表的倒数第N个节点
题目
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
解题思路
- 最简单的方法,遍历两遍,第一遍确定节点总数
num
,第二遍删除第num-n+1
个节点 - 另一种思路来自于代码随想录,使用双指针方法,如下图所示
- 对于临界值需要注意的是,如果是删除倒数第
n
个数,那么能确定至少有n
个数,临界值自然就是num = n
的情况
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode *fakeNode = new ListNode(0);
fakeNode->next = head;
ListNode *fastNode = fakeNode, *slowNode = fakeNode;
while (n && fastNode != nullptr) {
fastNode = fastNode->next;
n--;
}
fastNode = fastNode->next;
while (fastNode != nullptr) {
fastNode = fastNode->next;
slowNode = slowNode->next;
}
slowNode->next = slowNode->next->next;
return fakeNode->next;
}
};
142.环形链表II
题目
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0
开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
解题思路
(这题自己实在没搞出来,直接看大神的解析过程吧)
这类链表题目一般都是使用双指针法解决的,例如寻找距离尾部第 K 个节点、寻找环入口、寻找公共尾部入口等。
在本题的求解过程中,双指针会产生两次“相遇”。
双指针的第一次相遇:
设两指针 fast
,slow
指向链表头部 head
。
令 fast 每轮走 2
步,slow 每轮走 1
步。
执行以上两步后,可能出现两种结果:
- 第一种结果:
fast
指针走过链表末端,说明链表无环,此时直接返回null
。
如果链表存在环,则双指针一定会相遇。因为每走 1 轮,fast 与 slow 的间距 +1,fast
一定会追上slow
。 - 第二种结果: 当
fast == slow
时, 两指针在环中第一次相遇。下面分析此时fast
与slow
走过的步数关系:
设链表共有 a+b
个节点,其中 链表头部到链表入口 有 a
个节点(不计链表入口节点), 链表环 有 b
个节点(这里需要注意,a
和 b
是未知数,例如图解上链表 a=4
, b=5
);设两指针分别走了 f
,s
步,则有:
fast
走的步数是slow
步数的 2 倍,即f=2s
;fast
比slow
多走了n
个环的长度,即f=s+nb
;( 解析: 双指针都走过a
步,然后在环内绕圈直到重合,重合时fast
比slow
多走 环的长度整数倍 )。
将以上两式相减得到f=2nb,s=nb
,即fast
和slow
指针分别走了2n
,n
个环的周长。
接下来该怎么做呢?
如果让指针从链表头部一直向前走并统计步数k,那么所有 走到链表入口节点时的步数 是:k=a+nb
,即先走 a
步到入口节点,之后每绕 1 圈环( b
步)都会再次到入口节点。而目前 slow
指针走了 nb
步。因此,我们只要想办法让 slow
再走 a
步停下来,就可以到环的入口。
但是我们不知道 a
的值,该怎么办?依然是使用双指针法。考虑构建一个指针,此指针需要有以下性质:此指针和 slow
一起向前走 a
步后,两者在入口节点重合。那么从哪里走到入口节点需要 a
步?答案是链表头节点head
。
双指针第二次相遇:
- 令 fast 重新指向链表头部节点。此时
f=0,s=nb
。 slow
和fast
同时每轮向前走1
步。- 当
fast
指针走到f=a
步时,slow
指针走到s=a+nb
步。此时两指针重合,并同时指向链表环入口,返回slow
指向的节点即可。
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fastNode = head, *slowNode = head;
while (true) {
//先判断fastNode本身和其下一个节点是否为空
if (fastNode == nullptr || fastNode->next == nullptr) return nullptr;
//如果fastNode->next为空,则这里会报空指针异常
fastNode = fastNode->next->next;
slowNode = slowNode->next;
// 如果两节点相等,说明相遇,则退出循环
if (fastNode == slowNode) break;
}
// 已经相遇,说明是环。则fastNode指向头节点,再移动a步,则两节点相遇点必为环的起始点。
fastNode = head;
while (slowNode != fastNode) {
fastNode = fastNode->next;
slowNode = slowNode->next;
}
return fastNode;
}
};