LeetCode 141. 环形链表
题目描述
题意分析
给定链表头节点,判断链表中是否存在环,返回布尔值即可——本题不要求找出环的入口节点,那是 142 题的任务,这里只需要回答「有没有」。
题面中的
pos只用于评测端构造用例,并不会作为参数传入,函数能拿到的只有head。判断依据必须是节点引用(同一个节点被第二次到达),不能依赖节点值,因为不同节点完全可以有相同的值。进阶要求用常数内存解决,这是一个明确信号:存在不借助额外容器的判法。边界上,空链表和单节点无环链表(
next为空)都应返回false。
解法:Floyd 快慢指针
核心思路
慢指针每次走一步,快指针每次走两步。无环时快指针会先到达链表末尾;有环时两者进入环后,快指针会从后方追上慢指针。整个过程只需要两个指针。
解题步骤
- 将快、慢指针都初始化为头节点。
- 当快指针及其后继都非空时,慢指针走一步、快指针走两步。
- 移动后若两个指针引用相同,返回
true。- 循环正常结束说明快指针到达链表末尾,返回
false。
代码实现
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
}
func hasCycle(head *ListNode) bool {
slow := head
fast := head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
if slow == fast {
return true
}
}
return false
}
复杂度分析
- 时间复杂度:$O(n)$,每个指针的移动次数都与链表长度同阶。
- 空间复杂度:$O(1)$,只使用快、慢两个指针。
关键点总结
- 判断相遇必须比较节点引用,而不是节点值。
- 快指针每次走两步,循环条件必须同时检查
fast和fast.next。- 两个指针从同一起点出发时,应先移动再判断是否相遇。
易错点总结
- 只检查
fast != null就访问fast.next.next,可能触发空指针异常。- 移动前比较两个同起点的指针,会把任意非空链表误判为有环。
- 用节点值判断相遇,会把值相同的不同节点误认为同一节点。
- 本题只判断是否有环;寻找环入口还需要相遇后的第二阶段。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 142. 环形链表 II | 中等 | 相遇后推导环入口 |
| 202. 快乐数 | 简单 | 数字迭代构成的隐式链判环 |
| 287. 寻找重复数 | 中等 | 数组下标映射的隐式环 |
| 457. 环形数组是否存在循环 | 中等 | 带方向约束的环判定 |
| LCR 022. 环形链表 II | 中等 | 环入口题的镜像练习 |
| 面试题 02.08. 环路检测 | 中等 | 环入口结论的面试金典版 |