题目
给定链表头节点 head,判断链表中是否存在环。如果存在环则返回 true,否则返回 false。
示例:
有环情况:
3 → 2 → 0 → -4
↑ ↓
└───────┘
无环情况:
1 → 2 → 3 → null
解题思路
使用快慢指针(Floyd 判圈算法)来检测链表是否有环。
核心原理
设置两个指针:
- 慢指针 slow:每次移动 1 步
- 快指针 fast:每次移动 2 步
从同一起点出发,如果链表有环,快指针最终会在环内追上慢指针;如果无环,快指针会先到达链表末尾(null)。
为什么快慢指针一定会相遇?
如果有环,快指针进入环后,每次循环都会缩短与慢指针的距离 1 步,所以必然会相遇。可以理解为:在环形跑道上,跑得快的人一定会追上跑得慢的人。
代码实现
public class Solution {
public boolean hasCycle(ListNode head) {
// slow: 慢指针,每次走 1 步
// fast: 快指针,每次走 2 步
ListNode slow = head;
ListNode fast = head;
// 循环条件必须保护 fast.next 和 fast.next.next 不为空
// 否则执行 fast = fast.next.next 时会空指针异常
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
// 使用 == 判断是否是同一个节点对象
// 不能用 val 比较,因为不同节点值可能相同
if (slow == fast) {
return true;
}
}
return false;
}
}
代码解释
- 初始化双指针:slow 和 fast 都从 head 开始
- 循环条件:
fast != null && fast.next != null确保可以安全执行fast.next.next - 指针移动:slow 走 1 步,fast 走 2 步
- 相遇检测:用
==比较节点引用(不是比较值),相同则有环 - 退出循环:fast 到达 null 说明无环,返回 false
执行过程示例
以有环链表为例:
初始: slow=3, fast=3
第1轮:
slow = slow.next → slow=2
fast = fast.next.next → fast=0
第2轮:
slow = slow.next → slow=0
fast = fast.next.next → fast=2
第3轮:
slow = slow.next → slow=-4
fast = fast.next.next → fast=-4
slow == fast,返回 true
速记
快慢指针同起点,慢走 1 步快走 2 步,有环必相遇,无环快指针先到 null。
注意点
- 循环条件必须是
fast != null && fast.next != null:保护fast.next.next不会空指针 - 使用
==比较节点:比较的是引用地址,不是节点值,因为不同节点可能有相同的值 - 不能只判断
fast != null:会导致fast.next.next空指针异常 - 初始化位置:两个指针都从 head 开始,不需要错开
复杂度分析
- 时间复杂度:O(n)
- 无环:fast 遍历到链表末尾,最多 n/2 步
- 有环:slow 进入环后,fast 最多绕环一圈追上 slow
- 空间复杂度:O(1),只使用了两个指针变量