题目说明
题目:LeetCode 142. 环形链表 II
要求:给定一个链表头节点 head,判断链表是否有环;如果有环,返回环的入口节点;如果没有环,返回 nullptr。
方法一:快慢指针(Floyd 判圈)
思路
先用快慢指针判断是否有环:
- 慢指针每次走 1 步,快指针每次走 2 步。
- 如果链表无环,快指针会先到达
nullptr。 - 如果有环,快慢指针一定会在环内相遇。

相遇后,再从头节点出发一个指针,同时从相遇点出发一个指针,二者每次都走 1 步,再次相遇的位置就是环的入口。
为什么能找到入口
设:
- 头节点到环入口距离为
- 环入口顺时针到相遇点距离为
- 相遇点顺时针到环入口距离为
- 环长度为
已知:
联立得:
等价写法:
因此:
(即
结论:
从相遇点再走
代码
/**
* 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 *fast = head;
ListNode *slow = head;
while (fast != nullptr && fast->next != nullptr) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) {
ListNode *index1 = head;
ListNode *index2 = fast;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return nullptr;
}
};
复杂度分析
- 时间复杂度:
- 空间复杂度:
方法二:哈希表
思路
用哈希表记录访问过的节点地址:
- 遍历链表。
- 如果当前节点已存在于哈希表中,说明再次访问到了同一个节点,即存在环,且当前节点就是环入口。
- 如果不存在,则加入哈希表并继续遍历。
代码
class Solution {
public:
ListNode* detectCycle(ListNode* head) {
unordered_set<ListNode*> visited;
while (head != nullptr) {
if (visited.count(head)) {
return head;
}
visited.insert(head);
head = head->next;
}
return nullptr;
}
};
复杂度分析
- 时间复杂度:
- 空间复杂度:
总结
这题的核心是掌握 Floyd 判圈 + 数学关系推导入口。面试和笔试中更推荐方法一,因为它在时间复杂度相同的情况下只用常数额外空间;方法二更直观,适合快速验证思路。刷到链表环问题时,优先联想到快慢指针,通常就离 AC 不远了。