给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
不允许修改 链表。
示例 1:

输入: head = [3,2,0,-4], pos = 1 输出: 返回索引为 1 的链表节点 解释: 链表中有一个环,其尾部连接到第二个节点。
示例 2:

输入: head = [1,2], pos = 0 输出: 返回索引为 0 的链表节点 解释: 链表中有一个环,其尾部连接到第一个节点。
示例 3:

输入: head = [1], pos = -1 输出: 返回 null 解释: 链表中没有环。
提示:
- 链表中节点的数目范围在范围
[0, 10^4]内 -10^5 <= Node.val <= 10^5pos的值为-1或者链表中的一个有效索引
进阶: 你是否可以使用 O(1) 空间解决此题?
思路
朴素思路,可以
- 用一个 hash 表记录节点的入度,由于头节点也可能时环的入口,所以要在前面加一个哨兵节点。第一次入度到达 2 的就是环的入口。
- 或者更简单,用一个 hash set 记录每个节点是否被访问,第一个被访问两次的节点就是环的入口。
这样用了辅助空间,空间复杂度来到了 .
而题目就是让你用 空间解决。所以再进一步,我是想不到的。要用到一些数学计算。还是老朋友(链表常用技巧:快慢指针),定义两个指针 fast 和 slow。fast 每次走两步,slow 每次走一步。如果 fast 率先来到链表尾,说明无环。否则二者必然相遇(物理上来说 fast 相对于 slow 的速度为 1,再成环的链路上,必然追上 slow,也就是常说的套圈)。在有环的情况下,设从头节点到环入口需要走 步,环中有 个节点。
核心事实:快慢指针相遇时,slow 走了 步(也就是说 slow 走的步数是环中节点的整数倍)。参考灵神题解。
简单推导,设相遇时 fast 走了 步,slow 走了 步,由于 fast 的速度是 slow 的 2 倍,所以 。又因为 fast 比 slow 多走了 k 圈(扣掉从头到入环口的 步),故有 ,得 .
- fast = a + p
- slow = a + q
于是乎,相遇时 slow 相当于从入环口开始走了 步。因此,从相遇位置开始,再走 步即可到达入环口。但我们不知道 是多少,回想一下它的含义“从头节点到环入口需要走 步”。因此,在相遇时设一个指针从头结点开始,slow 和它相遇时,它们都到达了入环口。
Code
Floyd 判圈法
上面第二中优化思路成为 Floyd 判圈法,又称龟兔赛跑算法。
ListNode* detectCycle(ListNode* head) {
ListNode *slow = head, *fast = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) { // 有环
while (head != slow) { // 让head和slow一起走,相遇时即到达入环口
head = head->next;
slow = slow->next;
}
return head;
}
}
return nullptr; // fast走到链表尾,说明无环
}- 时间复杂度
- 空间复杂度
hash 表或集合辅助
// hash表记录入度
ListNode* detectCycle(ListNode* head) {
// 记录每个节点的入度,head可能为入环口,因此前面加个哨兵,直接体现为初始化入度为1
unordered_map<ListNode*, int> ingress_cnt = {{head, 1}};
for (auto p = head; p; p = p->next) {
if (++ingress_cnt[p->next] == 2) {
return p->next;
}
}
return nullptr;
}
// hash集合辅助
ListNode* detectCycle(ListNode* head) {
unordered_set<ListNode*> visited;
for (auto p = head; p; p = p->next) {
if (visited.count(p)) return p;
visited.insert(p);
}
return nullptr;
}- 时间复杂度
- 空间复杂度
补充证明
为什么 fast 比 slow 多走了 k 圈?
相遇时,slow 走了 s 步,fast 走 2s 步。相遇说明 s 和 2s 相对于入环口的位置是相等的。从入环口开始算,fast 跑了 步,slow 跑了 步。即,
所以相遇时,fast 比 slow 多走了 s 步,恰好是环长的整数倍。而 s 恰好又是 slow 走的步数!