快慢指针法可在o(n)时间、o(1)空间内判断链表是否有环,核心是慢指针每次走1步、快指针每次走2步,二者均从头节点出发,移动后再比较是否相遇,且必须先检查空指针和单节点边界。

快慢指针是检测链表是否存在环(即“死圈”)最经典、最节省空间的方法,核心在于只用两个指针、不借助哈希表或额外存储,就能在 O(n) 时间、O(1) 空间内完成判断。
初始化与移动规则必须严格对齐
慢指针从头节点出发,每次走 1 步;快指针也从头节点出发,每次走 2 步。关键不是“谁先动”,而是每次循环中,快指针必须完成两步跳跃(fast = fast->next->next)后再比对。若提前判断或顺序颠倒,比如在移动前就比较 fast == slow,会导致单节点链表直接返回 true,造成误判。
- 起始检查不能省:head 为 NULL 或 head->next 为 NULL 时,直接返回 false
- while 循环条件必须是 fast != NULL && fast->next != NULL,缺一不可——否则 fast->next->next 会访问非法内存
- 相遇判断必须放在移动之后,即先更新再比较
为什么走 2 步和 1 步能保证必相遇
这不是靠运气,而是由相对速度决定的确定性行为。一旦两个指针都进入环,快指针相对于慢指针每轮多走 1 步,相当于以“1 节点/轮”的速度在追赶。设此时两者在环内的距离为 d(d ≥ 1),那么最多 d 轮后必然重合。
- 若快指针走 3 步、慢指针走 1 步,相对速度为 2,当 d 是奇数且环长也是奇数时,可能陷入“差 1 步永远追不上”的情况
- 只有相对速度为 1(如 2:1、3:2、4:3……但 2:1 最简高效),才能覆盖所有初始距离 d ∈ [1, μ),μ 为环长
- 所以 2 步 vs 1 步不是随意选的,是数学上保障收敛的最小整数解
代码实现要避开三个典型陷阱
很多初学者写出的代码看似结构正确,却在边界上崩溃或漏判。以下是 C 风格伪码中必须注意的细节:
- 空指针访问陷阱:fast->next->next 前没确认 fast->next 非空 → 段错误
- 单节点误判陷阱:未在开头检查 head->next == NULL,导致 head 不为空但无后续时仍进入循环
- 初始相等陷阱:把 if (slow == fast) 放在 while 循环第一行,而 slow 和 fast 初始都指向 head → 永远返回 true
一个精简可靠的 C 函数模板
以下逻辑已通过 LeetCode #141 全量测试用例验证:
bool hasCycle(struct ListNode *head) {
if (!head || !head->next) return false;
struct ListNode *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) return true;
}
return false;
}
这个版本没有冗余变量、不修改原链表、不分配堆内存,完全满足“无需额外空间”的硬性要求。










