1 题目
给定一个链表的头节点 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
解释:链表中没有环。
提示:
- 链表中节点的数目范围在范围
[0, 104]内 -105 <= Node.val <= 105pos的值为-1或者链表中的一个有效索引
2 思路
实际上这是141. 环形链表 - 力扣(LeetCode)的进阶版


1 首先判断是否有环(可以按照easy的策略,快慢指针相遇)
2 返回环节点,按照图中的路径,其实是一点数学呢,相遇以后,其中一个指针到头节点,快慢指针以相同速度前进,再次项羽点就是环节点
3 注意非空,注意临界条件
3 代码实现(c)
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
ListNode *one = head ;
ListNode *two = head;
while(one != NULL && one-> next != NULL){
one = one -> next -> next ;
two = two -> next ;
if(one == two){
two = head ;
while(one != two){
one = one -> next;
two = two -> next;
}
return one ;
}
}
return NULL;
}
初版,失败的代码()
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
ListNode *one = head->next;
ListNode *two = head;
while(one != NULL){
one = one -> next ;//一开始是想让one,two做两个小跟班,实际上逻辑行不通
two = two -> next ;
if(one == two){
two = head ;
while(one != two){
one = one -> next;
two = two -> next;
if(one == two){
break;
}
}
return one;
}
}
return NULL;
}
代码中的问题
-
初始指针位置错误 代码中初始化为
one = head->next; two = head;,若链表为空(head == NULL),会直接访问head->next导致空指针异常。 -
快慢指针步长错误 检测环时,快指针应每次走 2 步,慢指针每次走 1 步(才能利用 "相对速度" 相遇)。但代码中
one(快指针)和two(慢指针)每次都只走 1 步(one = one->next; two = two->next;),相当于两个指针同速前进,若有环也可能永远不相遇。
正确的环起始节点检测逻辑
要找到环的起始节点,核心利用快慢指针相遇的性质:
-
检测环是否存在 :快指针(每次 2 步)和慢指针(每次 1 步)从表头出发,若相遇则有环;若快指针走到终点(
NULL)则无环。 -
找到环的起始节点:当快慢指针相遇时,将其中一个指针重置到表头,两者以相同速度(每次 1 步)前进,再次相遇的节点就是环的起始节点。
原理 :设表头到环起点距离为
a,环起点到相遇点距离为b,环长度为c。相遇时快指针走了a + b + k*c(k为圈数),慢指针走了a + b。由于快指针速度是慢指针的 2 倍,因此a + b + k*c = 2*(a + b),化简得a = k*c - b,即表头到环起点的距离 = 相遇点到环起点的距离(绕环k-1圈后)。因此两指针同速前进时,会在环起点相遇。
修正后的代码(示例)
cpp
struct ListNode *detectCycle(struct ListNode *head) {
if (head == NULL || head->next == NULL) {
return NULL; // 空链表或只有一个节点,无环
}
ListNode *slow = head;
ListNode *fast = head;
// 第一步:检测环是否存在(快慢指针相遇)
while (fast != NULL && fast->next != NULL) {
slow = slow->next; // 慢指针走1步
fast = fast->next->next; // 快指针走2步
if (slow == fast) { // 相遇,有环
// 第二步:找环的起始节点
slow = head; // 慢指针重置到表头
while (slow != fast) {
slow = slow->next;
fast = fast->next; // 同速前进,再次相遇即为环起点
}
return slow;
}
}
return NULL; // 无环
}
总结
- 原代码因指针初始位置和步长错误,无法正确检测环和起始节点。
- 正确逻辑需利用快慢指针的 "2 倍速差" 相遇检测环,再通过重置指针同速前进找到环起点。