算法概念
1、定义
判断单链表是否有环的快慢指针法(也叫龟兔赛跑算法),是效率最优的方案(时间 O (n)、空间 O (1)),核心逻辑是:通过两个速度不同的指针遍历链表,若链表有环,快指针终将追上慢指针;若无环,快指针会先走到链表末尾(指向 null)。
slow = slow->next;
fast = fast->next->next;
public class LinkedListCycle {
public boolean hasCycle(ListNode head) {
// 边界处理:空链表或只有一个节点,无环
if (head == null || head->next == null) {
return false;
}
// 初始化快慢指针(快指针先多走1步,避免初始状态相等)
ListNode slow = head;
ListNode fast = head->next; // 优化:初始错开,减少循环次数
// 循环条件:快指针未走到末尾
while (fast != null && fast->next != null) {
if (slow == fast) { // 相遇,有环
return true;
}
slow = slow->next; // 慢指针走1步
fast = fast->next->next; // 快指针走2步
}
// 快指针走到末尾,无环
return false;
}
}
2、常见疑问
① 快指针为什么在有环的情况下会追上慢指针?(为什么不会发生快指针直接超过慢指针的情况?)
答:当慢指针进入环后,快慢指针就处于同一个环形空间,两者之间的距离是 “环形距离”(范围:0 ~ 环长 L-1)。由于每轮速度差 1 步,这个环形距离只会每轮缩小 1 步(不是 “随机变化” 或 “跳跃式变化”)。
注意:快慢指针的速度之差只能在1步(如果环的大小 ≥ 快慢指针步数之差,会发生快指针直接超过慢指针的情况)。
② while()循环的条件怎么来的?(如果没有环,该怎么退出循环?)
答:
fast == null:快指针刚好到表尾
fast->next == null:快指针的下一个结点就是表尾,但是因为快指针一次性走两步,所以会访问fast->next->next但是fast->next已经是空指针了,会发生空指针访问。
3、进阶——怎么找到入口点(相遇点就是入口点?)
答:第一次相遇点不一定就是入口点!
让AI帮我解释一下,偷个懒@^_^@
推导
先定义 3 个核心变量,让推导更清晰:
- 设:链表头到环入口的距离为
a(步数);- 设:环的长度为
L(步数,即环内节点总数);- 设:快慢指针第一次相遇时,慢指针总共走了
k步。步骤 1:基于速度差推导
k与L的关系
- 快指针速度是 2 步 / 轮,慢指针是 1 步 / 轮,所以相遇时快指针走了
2k步;- 快慢指针的步数差:
2k - k = k步;- 由于相遇时快指针已经在环内循环了至少 1 圈(否则追不上慢指针),所以步数差
k一定是环长L的整数倍(比如 1 倍、2 倍...),即:k = n×L(n是正整数,代表快指针在环内转了n圈)。步骤 2:推导
a与相遇点的关系
- 慢指针走的
k步,可拆分为两部分:从表头到环入口的a步 + 从环入口到相遇点的b步(b < L,因为还没绕环 1 圈);- 所以:
k = a + b;- 结合步骤 1 的结论
k = n×L,可得:a + b = n×L→ 变形为:a = n×L - b;- 而
n×L - b的物理意义是:从相遇点出发,绕环走n×L - b步,恰好能到达环入口(因为b是相遇点到入口的步数,L - b是入口到相遇点的反向步数,n×L是绕环n圈,不影响最终位置)。步骤 3:为什么 “重置 + 同步移动” 能找到入口?
- 由
a = n×L - b可知:从表头到入口的距离a,等于从相遇点到入口的环形距离(n×L - b);- 此时若将慢指针重置为头节点,快指针留在相遇点,然后两者同时以 “1 步 / 轮” 移动:
- 慢指针从表头走
a步,会到达环入口;- 快指针从相遇点走
a步(即n×L - b步),也会到达环入口;- 因此,两者会在环入口点再次相遇 —— 这就是找到入口的核心逻辑!
通俗示例
假设:
a=3(头到入口 3 步),L=5(环长 5),n=1(快指针绕环 1 圈)。
- 慢指针走
k = n×L = 5步(相遇时):5 = 3(a) + 2(b)→ 相遇点在环内距离入口 2 步的位置;- 变形公式:
a = 1×5 - 2 = 3→ 从相遇点走 3 步到入口;- 操作:慢指针重置到表头,走 3 步到入口;快指针从相遇点走 3 步(2 步到入口 + 1 步绕环,因
n=1),两者同时到达入口,相遇。
// 找到环的入口节点(无环返回nullptr)
ListNode* detectCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return nullptr;
}
ListNode* slow = head;
ListNode* fast = head;
bool hasCycle = false;
// 第一步:找第一次相遇点(确认有环)
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) { // 第一次相遇,有环
hasCycle = true;
break;
}
}
// 无环则返回null
if (!hasCycle) {
return nullptr;
}
// 第二步:重置慢指针,同步移动找入口
slow = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next; // 现在快指针也走1步
}
return slow; // 再次相遇,即为入口
}
力扣例题
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode* head) {
if (head == NULL || head->next == NULL)
return false;
auto slow = head;
auto fast = head->next;
while (fast != NULL && fast->next != NULL) {
if (fast == slow)
return true;
slow = slow->next;
fast = fast->next->next;
}
return false;
}
};
2384

被折叠的 条评论
为什么被折叠?



