从公路跑步到链表成环:探索环形链表检测

生活中的环形

想象两个人在环形跑道上跑步,一个跑得快,一个跑得慢。如果他们一直跑下去,快的跑者一定会从后面追上慢的跑者。这就是我们今天要讨论的环形链表问题的现实映射。在跑道上,两个速度不同的跑者相遇就说明跑道是环形的;同样在链表中,如果两个速度不同的指针相遇,就说明链表中存在环。

问题描述

LeetCode第141题"环形链表"要求:给你一个链表的头节点 head,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。

例如:

输入:3 → 2 → 0 → -4
↑___________|
输出:true
解释:链表中存在一个环,尾节点连接到第二个节点 输入:1 → 2
↑___|
输出:true
解释:链表中存在一个环,尾节点连接到第一个节点 输入:1 → 2 → 3 → 4
输出:false
解释:链表中不存在环

简单解法:哈希表记录

最直观的想法是用一个哈希表记录每个走过的节点。就像在跑道上撒面包屑,如果遇到已经撒过面包屑的地方,说明路径形成了环。

哈希表解法实现

public boolean hasCycle(ListNode head) {
Set<ListNode> seen = new HashSet<>(); while (head != null) {
// 如果已经见过这个节点,说明有环
if (seen.contains(head)) {
return true;
}
// 记录当前节点
seen.add(head);
head = head.next;
} return false;
}

优化解法:快慢指针(Floyd判圈算法)

这个经典算法也被称为"龟兔赛跑算法",就像我们开始说的跑步场景:让一快一慢两个指针在链表上移动,如果存在环,快指针最终一定会追上慢指针。

为什么快慢指针一定会相遇?

想象在环形跑道上:

  1. 快指针每次走2步,慢指针每次走1步
  2. 相对来说,快指针每次都在追赶慢指针1步
  3. 如果有环,这就像在操场上追赶,快指针一定会追上慢指针
  4. 如果无环,快指针会先到达终点

代码实现与详解

public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
} // 初始化快慢指针
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;
}

图解过程

以一个有环链表为例:

1) 初始状态:
3 → 2 → 0 → 4
↑_________|
S,F
(S=slow, F=fast) 2) 第一次移动后:
3 → 2 → 0 → 4
↑_________|
S F 3) 第二次移动后:
3 → 2 → 0 → 4
↑_________|
S F 4) 最终相遇:
3 → 2 → 0 → 4
↑_________|
S,F

复杂度比较

哈希表解法:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n),需要存储已访问节点
  • 优点:思路直观,容易实现
  • 缺点:需要额外空间

快慢指针解法:

  • 时间复杂度:O(n)
  • 空间复杂度:O(1),只需要两个指针
  • 优点:空间效率高,实现优雅
  • 缺点:需要理解快慢指针的数学原理

核心原理解析

1. 数学证明

为什么快慢指针一定会相遇?

  • 假设环长为K,入环前长度为N
  • 慢指针走S步时,快指针走2S步
  • 快指针多走的S步一定是环长K的整数倍
  • 因此快慢指针一定会在入环后的K-N步内相遇

2. 临界情况分析

  • 空链表
  • 单节点链表
  • 环的长度为1(自环)
  • 入环点在开头或结尾

实用技巧总结

解决环形问题的关键点:

  1. 掌握快慢指针技巧
  2. 理解环形结构的特性
  3. 考虑边界情况
  4. 注意指针移动的顺序

相关的环形问题:

  • 找到环的入口点
  • 计算环的长度
  • 找到环中的特定节点

小结

环形链表的检测问题是链表操作中的一个经典问题。它教会我们:

  1. 如何用最小的空间解决复杂问题
  2. 快慢指针这个强大的算法技巧
  3. 如何将现实问题映射到算法思维
  4. 优雅解法往往来自于深刻的数学原理

建议:多思考快慢指针的应用场景,它不仅用于检测环,还可以:

  • 找到链表中点
  • 判断链表是否为回文
  • 找到倒数第K个节点

这些问题都可以用类似的思维方式来解决!


作者:忍者算法

公众号:忍者算法

我准备了一份刷题清单,以及这些题目的详细题解,覆盖了绝大部分常见面试题。我可以很负责任地说,只要你把这些题真正掌握了,80%的算法面试都能遇到相似题目。公众号回复【刷题清单】获取~

【忍者算法】从公路跑步到链表成环:探索环形链表检测|LeetCode第141题 环形链表的更多相关文章

  1. 【python】Leetcode每日一题-旋转链表

    [python]Leetcode每日一题-旋转链表 [题目描述] 给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置. 示例1: 输入:head = [1,2,3,4,5] ...

  2. 【python】Leetcode每日一题-反转链表 II

    [python]Leetcode每日一题-反转链表 II [题目描述] 给你单链表的头节点 head 和两个整数 left 和 right ,其中 left <= right .请你反转从位置 ...

  3. Leetcode 141题 环形链表(Linked List Cycle) Java语言求解

    题目描述: 给定一个链表,判断链表中是否有环. 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始). 如果 pos 是 -1,则在该链表中没有环. Map ...

  4. [Leetcode] 第148题 排序链表

    一.题目描述 在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序. 示例 1: 输入: 4->2->1->3 输出: 1->2->3->4 示 ...

  5. 【LeetCode题解】142_环形链表2(Linked-List-Cycle-II)

    目录 描述 解法一:哈希表 思路 Java 实现 Python 实现 复杂度分析 解法二:双指针 思路 Java 实现 Python 实现 复杂度分析 描述 给定一个链表,返回链表开始入环的第一个节点 ...

  6. LeetCode 上最难的链表算法题,没有之一!

    题目来源于 LeetCode 第 23 号问题:合并 K 个排序链表. 该题在 LeetCode 官网上有关于链表的问题中标注为最难的一道题目:难度为 Hard ,通过率在链表 Hard 级别目前最低 ...

  7. 《数据结构与算法之美》 <05>链表(下):如何轻松写出正确的链表代码?

    想要写好链表代码并不是容易的事儿,尤其是那些复杂的链表操作,比如链表反转.有序链表合并等,写的时候非常容易出错.从我上百场面试的经验来看,能把“链表反转”这几行代码写对的人不足 10%. 为什么链表代 ...

  8. 【算法训练营day4】LeetCode24. 两两交换链表中的结点 LeetCode19. 删除链表的倒数第N个结点 LeetCode面试题 02.07. 链表相交 LeetCode142. 环形链表II

    [算法训练营day4]LeetCode24. 两两交换链表中的结点 LeetCode19. 删除链表的倒数第N个结点 LeetCode面试题 02.07. 链表相交 LeetCode142. 环形链表 ...

  9. c++ LeetCode (网易面试题和链表以及树篇) 五道算法例题代码详解(三)

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/11209807.html 一.1道网易c++的面试题 我当时第一时间的解答方案 #include ...

  10. 【LeetCode题解】141_环形链表

    目录 141_环形链表 描述 解法一:哈希表 思路 Java 实现 Python 实现 解法二:双指针(龟兔算法) 思路 Java 实现 Python 实现 141_环形链表 描述 给定一个链表,判断 ...

随机推荐

  1. BeautifulSoup(bs4)细致讲解

    BeautifulSoup(bs4) BeautifulSoup是python的一个库,最主要的功能是从网页爬取数据,官方是这样解释的:BeautifulSoup提供一些简单,python式函数来处理 ...

  2. a标签与Blob下载文件的区别和获取文件下载进度

    文件下载的几种方式. 大家都做过文件下载,无非就是通过a标签给定一个href. 用户点击下载按钮. 或者使用Blob的方式进行下载. 这两种是很常见的,也是我们平时做使用最多的方式. 那么我们知道这2 ...

  3. 如何在 Epicor 中计算绩效

    制造性能是任何生产工序的关键,允许企业衡量和评估其效率和生产力水平. 我们将探讨如何在 Epicor 中计算制造性能.计算整体设备效率(OEE) 时性能指标的价值.如何解释制造指标以及在 Epicor ...

  4. HarmonyOS Next 入门实战 - 基础组件、页面实现

    基础组件 常用组件 Text:显示文本内容 Image:显示图片 Button:显示一个按钮 Column: 纵向布局 Row:横向布局 List:列表 各组件的用法 Text("文本组件& ...

  5. 推荐7个最强Appium替代工具,移动App自动化测试必备!

    在移动应用开发日益火爆的今天,自动化测试成为了确保应用质量和用户体验的关键环节.Appium 作为一款广泛应用的移动应用自动化测试工具,为测试人员所熟知.然而,在不同的测试场景和需求下,还有许多其他优 ...

  6. [NET,C# ] Nuget包发布流程

    1.新建一个.NET Core类库 2.新增一个方法,并编译项目 3.下载Nuget.exe,与刚才新建的类库放在同一目录下 下载地址:https://www.nuget.org/downloads ...

  7. Trino 436 - 使用教程(亲测,详细)

    第一章 Trino简介 1. Trino概述 Trino是一个分布式SQL查询引擎,旨在查询分布在一个或多个异构数据源上的大型数据集.如果使用的是数TB或数PB的数据,那么很可能使用的是与Hadoop ...

  8. Oracle.DataAccess.Client.OracleException: 提供程序与此版本的 Oracle 客户机不兼容

    背景:进行程序部署,客户机上原有oracle客户端的版本为2.113.1.0(以下简称113),而数据库.开发机和其他客户机上均采用的2.112.1.0(以下简称112)客户端,所以进行了替换. 卸载 ...

  9. 渗透测试-前端加密分析之AES

    前言 本文是高级前端加解密与验签实战的第3篇文章,本系列文章实验靶场为Yakit里自带的Vulinbox靶场,本文讲述的是绕过前端 AES(CBC) 和 AES(ECB) 加密. 因为编写Yakit热 ...

  10. ruoyi若依前端验证码不显示的终极解决方法-20230721

    ​搞了3天啊,查了各种资料啊. 然后使劲的看log啊,总算搞定了啊. 一般情况,本地开发环境测试没问题,部署到服务器就各种不适应,就是服务器配置的问题了. 本次这种验证码不显示,典型的nginx的配置 ...