环形链表检测与入口查找
字数 1378 2025-12-14 04:48:07
环形链表检测与入口查找
题目描述
给定一个链表的头节点 head,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。如果存在环,还需要找出环的入口节点(即链表开始入环的第一个节点)。这是LeetCode中“环形链表II”(#142)的经典问题。
知识背景与重要性
这是链表操作中的核心问题,考察对链表结构、快慢指针(Floyd判圈算法)的理解。在工程中,该算法可用于检测资源依赖中的循环引用、状态机中的死循环等。
解题思路:Floyd判圈算法(快慢指针法)
第一步:判断链表是否有环
- 初始化:设置两个指针,
slow(慢指针)每次移动一步,fast(快指针)每次移动两步。都从头节点head开始。 - 移动与判断:在循环中,每次移动
slow一步,fast两步。- 如果
fast或其next为null,说明链表无环,可以返回null。 - 如果
slow和fast相遇,则链表一定有环。此时进入第二步。
- 如果
原理:想象两个人在环形跑道上跑步,快的人速度是慢的人的两倍。他们从同一起点出发,因为跑道是环形的,快的人最终一定会从后面追上慢的人(即相遇)。
第二步:寻找环的入口
- 定位相遇点:第一步结束后,
slow和fast在环内某点相遇。 - 重置指针:将一个指针(例如
ptr1)重新指向链表头head,另一个指针ptr2保持在相遇点。 - 同步移动:两个指针都改为每次移动一步。当它们再次相遇时,相遇点即为环的入口节点。
原理解析(需要一点数学推导):
- 设链表头到环入口的距离为
F,环入口到第一步中相遇点的距离为a,相遇点再回到环入口的距离为b,那么环的周长C = a + b。 - 第一步相遇时:
slow指针走过的距离为F + a。fast指针走过的距离为F + a + n*C,其中n是fast在环内绕的圈数(n >= 1)。- 因为
fast速度是slow的两倍,所以有:2(F + a) = F + a + n*C。 - 化简得:
F = n*C - a = (n-1)*C + b。
- 第二步同步移动:
ptr1从head出发走F步到达入口。ptr2从相遇点出发,走F步。根据上式F = (n-1)*C + b,ptr2先走b步到达入口,再绕(n-1)圈,最终也停在入口。因此两者在入口处相遇。
代码实现(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; // 也可以使用fast,此时它们位置相同
while (ptr1 != ptr2) {
ptr1 = ptr1.next;
ptr2 = ptr2.next;
}
return ptr1; // 返回环的入口节点
}
}
// fast遇到null,说明无环
return null;
}
}
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
复杂度分析
- 时间复杂度:O(N)。在第一步中,
slow指针在相遇前最多走F + a步(小于链表总长度)。第二步中,两个指针最多走F步。总体是线性复杂度。 - 空间复杂度:O(1)。只使用了几个固定数量的指针变量。
关键点总结
- 核心思想:快慢指针是解决环形链表问题的标准且最优方法。
- 相遇一定有环:在有环链表中,快指针不会“跳过”慢指针,因为它们的速度差是1。
- 入口公式推导:理解
F = (n-1)*C + b是理解算法为何第二步能定位入口的关键。 - 边界条件处理:注意处理空链表、单节点无环等情况。