题目:
给定一个链表的头节点 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
解释:链表中没有环。
核心思路
快慢指针两阶段法:
-
第一阶段:快慢指针找到相遇点(证明有环)
-
第二阶段:一个指针从头开始,一个从相遇点开始,再次相遇的地方就是环入口
链表:
1 → 2 → 3 → 4 → 5
↑ ↓
└───────────┘
环入口是节点2
题解:
java
public class Solution {
public ListNode detectCycle(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;
fast = fast.next.next;
if (slow == fast) {
// 找到相遇点,进入第二阶段
// 一个指针从头开始,一个从相遇点开始
ListNode ptr1 = head;
ListNode ptr2 = slow;
while (ptr1 != ptr2) {
ptr1 = ptr1.next;
ptr2 = ptr2.next;
}
return ptr1; // 环的入口
}
}
return null; // 无环
}
}
```
## 数学原理(重要!)
### 设定变量
```
链表结构:
head → ... → 入口 → ... → 相遇点 → ... → 入口
a步 b步 c步
a: 起点到入口的距离
b: 入口到相遇点的距离
c: 相遇点回到入口的距离
环长 L = b + c
```
### 推导过程
```
第一次相遇时:
慢指针走的距离: a + b
快指针走的距离: a + b + n * L (n是快指针在环内转的圈数)
因为快指针速度是慢指针的2倍:
2 * (a + b) = a + b + n * L
化简:
2a + 2b = a + b + n * L
a + b = n * L
a = n * L - b
a = n * (b + c) - b
a = (n - 1) * (b + c) + c
a = (n - 1) * L + c
结论:
从起点走 a 步 = 从相遇点走 c 步 + (n-1)圈
所以:
- 一个指针从 head 出发
- 一个指针从相遇点出发
- 两者每次走1步,会在环入口相遇!
```
## 详细演示
```
链表:
3 → 2 → 0 → -4
↑ ↓
└────────┘
a = 1 (head到入口:3→2)
b = 1 (入口到相遇点可能是:2→0)
c = 2 (相遇点回到入口:0→-4→2)
L = 3 (环长:2→0→-4→2)
第一阶段: 找相遇点
------------------
初始:
slow = 3
fast = 3
第1次移动:
slow: 3 → 2
fast: 3 → 2 → 0
slow = 2
fast = 0
第2次移动:
slow: 2 → 0
fast: 0 → -4 → 2
slow = 0
fast = 2
第3次移动:
slow: 0 → -4
fast: 2 → 0 → -4
slow = -4
fast = -4
相遇!
第二阶段: 找环入口
------------------
ptr1 = head = 3
ptr2 = slow = -4
第1次移动:
ptr1: 3 → 2
ptr2: -4 → 2
ptr1 = 2
ptr2 = 2
相遇!
返回 节点2(环入口)
```
## 图解过程
```
链表:
入口
↓
1 → 2 → 3 → 4 → 5
↑ ↓
└───────────┘
步骤1: 快慢指针找相遇点
--------------------
slow每次1步,fast每次2步
假设在节点4相遇:
入口
↓
1 → 2 → 3 → 4 → 5
↑ ↑ ↓
└───────┴───┘
相遇点
步骤2: 从head和相遇点同时出发
--------------------
ptr1从1出发,ptr2从4出发,每次各走1步
ptr1: 1 → 2
ptr2: 4 → 5 → 2
↑
相遇在入口!
```
## 为什么第二阶段要这样走?
### 直观理解
```
验证数学推导 a = c + (n-1)L:
从head到入口的距离 = a
从相遇点绕环回到入口的距离 = c
因为 a = c + (n-1)L
所以从head走a步到入口
= 从相遇点走c步到入口 + 再转(n-1)圈
两个指针每次都走1步:
- ptr1走a步到达入口
- ptr2走c步+(n-1)圈也到达入口
- 所以两者在入口相遇!
```
### 特殊情况:n=1
```
当快指针只转了1圈就相遇(最常见):
a = c
这时:
- 从head走到入口的距离
- 等于从相遇点走到入口的距离
- 两者必然同时到达入口
```
## 完整示例
### 示例1
```
链表:
3 → 2 → 0 → -4
↑ ↓
└────────┘
第一阶段:
slow和fast在某处相遇(假设在-4)
第二阶段:
ptr1从3开始: 3 → 2
ptr2从-4开始: -4 → 2
在节点2相遇 ✓(环入口)
```
### 示例2
```
链表:
1 → 2
↑ ↓
└───┘
第一阶段:
slow和fast在2相遇
第二阶段:
ptr1从1开始: 1
ptr2从2开始: 2 → 1
在节点1相遇 ✓(环入口)
```
### 示例3(无环)
```
链表: 1 → 2 → 3 → null
第一阶段:
fast到达null,没有相遇
直接返回 null