题目描述

✅ 141. 环形链表

image-20260928184552114

image-20260928184552115

题意分析

从头节点开始不断沿 next 向后走,如果会再次到达已经访问过的同一个节点,链表就有环;如果最终走到空节点,就没有环。返回是否存在环,不需要返回环的入口,也不能通过修改指针来做标记。

这里的“同一个节点”指同一个对象,节点值相等不能说明有环。空链表没有环;只有一个节点时,要看它的后继是否指回自身,不能仅凭节点数量判断。

解法:Floyd 快慢指针

核心思路

[!blue]

用集合记录经过的节点可以判断重复访问,但需要额外空间。要省掉这份记录,可以让两个指针以不同速度沿同一条链移动:slow 每轮走一步,fast 每轮走两步,观察它们是否会在移动后到达同一个节点。

如果没有环,链表是一条有限路径。快指针不断前进,最终一定会到达空节点,或停在后继为空的尾节点,因此循环能够结束。

如果存在环,快指针不会走到空节点;当慢指针进入环时,快指针也已经在环中。设环长为 c,每轮快指针相对慢指针多走一步,两者的位置差按模 c 依次变化,至多再走 c 轮就会变成 0,也就是相遇。这说明有环时一定能找到相遇点,不需要无限等待。

两个指针都从头节点出发,初始相同没有判定意义,所以必须先移动再比较。比较的是节点引用;一旦移动后相遇,就说明快指针通过环绕重新追上了慢指针。

解题步骤

  1. 令 slow、fast 都指向 head。
  2. 每轮先检查 fast 和 fast.next 都非空,保证两步移动不会解引用空节点。
  3. 将 slow 移动到后继,将 fast 移动到后继的后继。
  4. 移动后若 slow == fast,说明有环,立即返回 true。
  5. 若循环因为快指针走到末尾而结束,返回 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
}

复杂度分析

设 n 为从头节点出发能够到达的不同节点数。

  • 时间复杂度:$O(n)$。无环时快指针最多走完链表;有环时,慢指针先经过环外前缀,再经过至多一圈就会与快指针相遇。
  • 空间复杂度:$O(1)$,只使用快、慢两个指针。

关键点总结

[!green]

  • 两个指针速度不同,才能让环内的相对位置持续变化,并保证最终相遇。
  • 快指针是否走到空节点区分了无环情况,移动后是否相遇区分了有环情况。
  • 判断相遇只比较节点身份,不依赖节点值,也不修改链表。

易错点总结

[!yellow]

  • 只检查 fast != null 就访问 fast.next.next,在快指针位于尾节点时会触发空指针异常。
  • 在第一次移动前比较两个指针,会把它们初始都指向头节点误当成有环证据。
  • 用节点值判断相遇,会把值相同的不同节点误认为同一节点。
  • 相遇点不一定是环入口。本题只返回布尔值,不能把这段代码的相遇位置直接当作另一道题要求的入口。

相似题目

题目 难度 关联与区别
142. 环形链表 II 中等 快慢指针相遇提供有环判定,原题进一步把指针移回链头求入环点。
202. 快乐数 简单 把数字变换视为下一状态,同样可用快慢指针判断过程是否进入循环。
287. 寻找重复数 中等 用快慢指针确定环或中间位置;本题判断链表是否存在环,该题将数组值视为后继指针寻找重复入口。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/13009476
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!