【LeetCode 热题 100】141. 环形链表——快慢指针

Problem: 141. 环形链表

题目:给你一个链表的头节点 head ,判断链表中是否有环。

文章目录

整体思路

这段代码旨在解决一个经典的链表问题:环形链表 (Linked List Cycle) 。问题要求判断一个给定的单链表是否存在环,即链表中某个节点的 next 指针是否指向了它前面已经出现过的节点。

该算法采用了大名鼎鼎的 Floyd判圈算法(Floyd's Cycle-Finding Algorithm) ,也常被称为 "快慢指针""龟兔赛跑" 算法。这是一种极其高效且空间优化的方法。

  1. 核心思想:速度差追及

    • 算法的逻辑非常直观,就像在一个环形跑道上进行龟兔赛跑。
    • 设置两个指针,一个"慢"指针 slow 和一个"快"指针 fast,都从链表的头节点 head 出发。
    • slow 指针每次移动一步。
    • fast 指针每次移动两步。
    • 如果链表中存在环fast 指针会先进入环,并在环内不断地绕圈。slow 指针稍后也会进入环。一旦两个指针都进入了环,fast 指针由于速度更快,它会从后面"追上"slow 指针。它们最终必然会在环内的某个节点相遇。
    • 如果链表中不存在环fast 指针由于走得更快,它会首先到达链表的末尾(即 fastfast.next 变为 null)。一旦它到达末尾,就说明整个链表已经被遍历完且没有环。
  2. 算法实现步骤

    • 初始化 slowfast 指针都指向 head
    • 进入一个 while 循环。循环的条件 fast != null && fast.next != null 是至关重要的:
      • fast != null:确保 fast 指针本身是有效的。
      • fast.next != null:确保 fast 还能再走一步(因为 fast 每次要走两步 fast.next.next)。如果这个条件不满足,说明链表已经到头了。
    • 在循环内部:
      a. 移动指针slow = slow.next;fast = fast.next.next;
      b. 判断相遇 :在移动后,立即检查 slowfast 指针是否指向了同一个节点 (if (slow == fast))。
      • 如果它们相等,说明快指针追上了慢指针,证明链表中存在环。立即返回 true
    • 如果 while 循环正常结束(即因为 fast 到达链表末尾而终止),则说明在整个遍历过程中快慢指针没有相遇。因此,链表中不存在环,返回 false

完整代码

java 复制代码
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    /**
     * 判断一个单链表是否存在环。
     * @param head 链表的头节点
     * @return 如果存在环则返回 true,否则返回 false
     */
    public boolean hasCycle(ListNode head) {
        // 初始化慢指针 slow
        ListNode slow = head;
        // 初始化快指针 fast
        ListNode fast = head;
        
        // 循环条件:只要快指针及其下一个节点不为 null,就可以继续移动
        while (fast != null && fast.next != null) {
            // 慢指针每次移动一步
            slow = slow.next;
            // 快指针每次移动两步
            fast = fast.next.next;
            
            // 核心判断:如果快慢指针相遇,则证明存在环
            if (slow == fast) {
                return true;
            }
        }
        
        // 如果循环结束,说明快指针到达了链表末尾,不存在环
        return false;
    }
}

时空复杂度

时间复杂度:O(N)

  1. 无环情况 :如果链表没有环,快指针 fast 会先到达链表末尾。它走过的节点数大约是 N,而慢指针 slow 走了 N/2。总的来说,算法对链表进行了一次线性扫描。时间复杂度为 O(N)
  2. 有环情况
    • 设链表头到环入口的长度为 A,环的长度为 C
    • 当慢指针到达环入口时,它走了 A 步。此时,快指针已经在环内走了 A 步。
    • 此时,快指针相对于慢指针在环内的位置领先 A % C 步。
    • 慢指针需要被快指针追上。快指针每走一步,与慢指针的距离缩小 1。要追上 C - (A % C) 的距离,最多需要 C 步。
    • 因此,总的步数大约是 A (慢指针入环) + C (在环内追及),即 A+C。由于 A+C <= N,所以时间复杂度仍然是 O(N)

综合分析

无论链表是否有环,算法的时间复杂度都与链表的节点数 N 成线性关系,即 O(N)

空间复杂度:O(1)

  1. 主要存储开销 :该算法没有创建任何与输入规模 N 成比例的新的数据结构。
  2. 辅助变量 :只使用了 slowfast 两个指针变量来遍历链表。这些变量的数量是固定的,与链表长度无关。

综合分析

算法所需的额外辅助空间是常数级别的。因此,其空间复杂度为 O(1)。这是解决此问题最优的空间效率。

相关推荐
阳明山水3 小时前
校准分位数驱动智能库存决策
人工智能·深度学习·算法·机器学习·架构
光依旧3 小时前
MCP实战手记系列(五):让工具返回一个能点的界面(文末附github源码链接)
java·spring ai·前端集成·mcp·mcp apps
APIshop3 小时前
淘宝选品接口实战:从召回、详情、佣金到转链的 Java 接入笔记
java
码行山野赴时序归途3 小时前
链表家族收官篇:循环链表
c语言·开发语言·数据结构·链表
泡茶喝茶写代码3 小时前
量化数据进阶:多维实战篇(第 11 篇):历史涨跌停价:涨跌停序列与止损线测算
java·python·股票数据api·股票数据·股票数据api接口·股票api数据接口·股票量化数据api
景熙55233 小时前
单体项目编写思路和落地形式
java·开发语言
Ivanqhz3 小时前
Slope One 算法详解:与矩阵分解、共现矩阵的异同
java·服务器·网络·人工智能·深度学习
别动我齐刘海4 小时前
ROS2 Jazzy + C++ 实战路线——进阶学习3
c++·人工智能·vscode·python·算法·机器学习·机器人
Bs_MoneyMagnet4 小时前
基于springboot+vue的非遗物质文化遗产系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring·毕业设计·计算机毕业设计
万物智能4 小时前
波形发生AD9833模块配置—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
java