环形链表
题干
题目:给定一个链表的头节点 head,返回链表开始入环的第一个节点。 如果链表无环,则返回 null 。不允许修改链表。
如何判定存在环?
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 - 1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
思路
先确定链表有环,再找到环的入口结点。
1)如何判断是否有环?
不知道有什么方法,直接看的题解。
快慢指针法:一个快指针 fast,一个慢指针 slow,fast 每次移动两个结点,slow 每次移动一个结点。如果链表中没有环,则快慢指针不可能相遇;如果链表有环,则 fast 和 slow 会相遇。为什么链表有环,快慢指针就一定会相遇呢?不会错过吗?
首先 fast 肯定会比 slow 先进入环,而 fast 的速度是每次移动两个结点,slow 的速度是每次移动一个结点,fast 相对于 slow 每次前进( 2 - 1 = 1 )个结点,则进入环以后,fast 肯定会以每次移动一个结点的速度追上 slow。
力扣中也有单纯判断有无环的题目:. - 力扣(LeetCode)

2)如何找到环的入口结点?
根据快慢指针法,fast 和 slow 相遇的结点位置和环的入口位置有什么关系呢?
在判断有无环的时候我们能找到快慢指针相遇的结点,设置两个指针 index1、index2 分别从相遇点、链表起点处出发,在某段时间后 index1 和 index2 一定会在入口处相遇。为什么?语言不好描述,直接看卡哥的视频,自己跟着画图比较好理解。
视频讲解:把环形链表讲清楚! 如何判断环形链表?如何找到环形链表的入口? LeetCode:142.环形链表II_哔哩哔哩_bilibili

代码
仅判断有无环的代码
class Solution {
public:
bool hasCycle(ListNode *head) {
// head 可能为空指针
if (head == nullptr){
return false;
}
ListNode* fast = head;
ListNode* slow = head;
while (fast->next != nullptr && fast->next->next != nullptr){
// 当 fast->next 为空指针时说明已经遍历到链表末尾
fast = fast->next->next; // fast 每次走两个结点
slow = slow->next; // slow 每次走一个结点
if (fast == slow){
return true;
}
}
// 最后跳出while循环,则fast的下一个结点或下下个结点为空,说明链表没有环,末尾结点的 next 为空指针
return false;
}
};
进阶:判断环的入口在哪
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
// head 有可能是空指针,不判空会报错
if (head == nullptr){
return nullptr;
}
ListNode* fast = head;
ListNode* slow = head;
ListNode* index1 = head;
ListNode* index2 = head; // 记录相遇的结点
while (fast->next != nullptr && fast->next->next != nullptr){
fast = fast->next->next;
slow = slow->next;
if (fast == slow){
// 此时证明有环,且找到了相遇的位置
index2 = fast;
// 分别让 index1 和 index2 从头节点、相遇结点处出发
// 一段时间后两者会在环的入口处相遇
while (index1 != index2){
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return nullptr;
}
};
总结
链表的定义和基础操作
链表的定义主要是对节点数据域和指针域的定义,同时要注意构造函数的写法。
// 链表节点的定义
struct ListNode{
int val;
ListNode* next;
ListNode(int x) : val(x),next(nullptr){};
};
链表的基础操作:链表的初始化(虚拟头节点)、查找第 n 个节点的元素、头插法、尾插法、删除第 n 个节点的元素、在第 n 个节点前插入元素
题目和方法汇总
总共做了七道链表的题目,分别是:移除链表元素为某个定值的所有结点、链表设计、反转链表、两两交换链表中的结点、删除链表的倒数第 n 个结点、找到两个链表的相交结点、找到环形链表的入口结点。涉及到的方法有:双指针法、设置虚拟头节点。
为什么要设置虚拟头节点?
移除链表元素和链表设计两道题目都是为了掌握链表的增删改查操作以及虚拟头节点的使用。
不管是插入或删除操作,都需要找到当前结点的上一个结点,而头节点是没有上一个节点的,如果不设置虚拟头节点,则头节点和非头节点的操作无法统一。
双指针法
在代码随想录里,有一句话说得好,对链表的操作本质上是考察指针的操作。因为链表不像数组一样方便快速查找,指针是我们找到其中某个节点的关键,无论是删除、插入或找到相遇节点,本质都是要找到对应节点的指针,所以很多题目都能用双指针法解决。
反转链表、两两交换结点、删除链表的倒数第 n 个结点,都使用双指针法,只不过两个指针的间隔节点数不同。环形链表中,判断有无环也用到了双指针法( fast 和 slow 指针 )。
报错汇总
操作空指针报错
ERROR: member access within null pointer of type 'ListNode'
如果强行取 空指针 所指节点的值 ,相当于操作空指针,编译会报错。尤其是要注意题目中头节点是否有可能是空指针,如果是则需要先判断头节点是否为空。
试图访问已经被释放内存的结点
ERROR: AddressSanitizer: heap-use-after-free on address
AddressSanitizer是内存错误检测工具,此报错表明程序试图访问已经被释放的堆内存。假如前面有个节点已经被释放,而后续又试图再次访问该节点则会报此错误。

1044

被折叠的 条评论
为什么被折叠?



