Leetcode 31
1 题目
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 解释:链表中没有环。
提示:
- 链表中节点的数目范围在范围
[0, 104]
内 -105 <= Node.val <= 105
pos
的值为-1
或者链表中的一个有效索引
2 思路
实际上这是141. 环形链表 - 力扣(LeetCode)的进阶版
1 首先判断是否有环(可以按照easy的策略,快慢指针相遇)
2 返回环节点,按照图中的路径,其实是一点数学呢,相遇以后,其中一个指针到头节点,快慢指针以相同速度前进,再次项羽点就是环节点
3 注意非空,注意临界条件
3 代码实现(c)
/*** 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;}
初版,失败的代码()
/*** 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
圈后)。因此两指针同速前进时,会在环起点相遇。
修正后的代码(示例)
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 倍速差” 相遇检测环,再通过重置指针同速前进找到环起点。