当前位置: 首页 > news >正文

LeetCode 142. 环形链表 II - 最优雅解法详解

文章目录

  • LeetCode 142. 环形链表 II - 最优雅解法详解
    • 📋 问题描述
      • 🔗 约束条件
    • 🎯 核心算法 - 改进的Floyd循环检测
    • 🧮 算法原理深度解析
      • 🔍 为什么这个变种算法是正确的?
        • 第一阶段分析
        • 第二阶段补偿策略
      • 📐 数学验证
    • 🎭 算法流程图
    • 🌟 算法优势
      • ✅ 优点
      • 🎯 与标准Floyd算法的比较
    • 🧪 示例演示
      • 示例 1:[3,2,0,-4], pos = 1
      • 示例 2:[1,2], pos = 0
    • 🎪 核心洞察
      • 💡 关键理解
      • 🎭 编程哲学
    • 📊 复杂度分析
      • ⏰ 时间复杂度:O(n)
      • 💾 空间复杂度:O(1)
    • 🚀 实现技巧
      • 🔧 关键代码片段
      • ⚠️ 常见陷阱
    • 🎓 总结与思考
      • 🏆 算法价值
      • 🤔 深度思考
      • 🎯 学习建议

LeetCode 142. 环形链表 II - 最优雅解法详解

📋 问题描述

给定一个链表的头节点 head,返回链表开始入环的第一个节点。如果链表无环,则返回 null

🔗 约束条件

  • 链表中节点的数目范围在 [0, 10⁴] 内
  • -10⁵ <= Node.val <= 10⁵
  • pos 的值为 -1 或者链表中的一个有效索引
  • 不允许修改链表
  • 进阶要求:使用 O(1) 空间复杂度

🎯 核心算法 - 改进的Floyd循环检测

这是一个基于经典Floyd算法的优雅变种,通过调整指针初始位置和补偿策略,实现了更直观的环检测逻辑。

public ListNode detectCycle(ListNode head) {if (head == null || head.next == null) {return null;}// 第一阶段:检测环的存在ListNode slow = head;ListNode fast = head.next;  // 关键:快指针从head.next开始while (slow != fast) {if (fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;} else {return null;  // 无环}}// 第二阶段:定位环的入口fast = fast.next;  // 关键:补偿快指针多走的那一步slow = head;       // 慢指针重置到头部while (slow != fast) {slow = slow.next;fast = fast.next;}return slow;
}

🧮 算法原理深度解析

🔍 为什么这个变种算法是正确的?

第一阶段分析

标准Floyd算法:

  • 两个指针都从 head 开始
  • 相遇时:slow走了 a+b,fast走了 a+2b+c
  • 速度关系:a+2b+c = 2(a+b)c = a

我们的变种算法:

  • slow从 head 开始,fast从 head.next 开始
  • 相遇时:slow走了 a+b,fast走了 a+2b+c+1(多了初始的1步)
  • 速度关系:a+2b+c+1 = 2(a+b)c = a-1
第二阶段补偿策略

由于 c = a-1,如果直接让slow从head开始、fast保持原位,它们不会在环入口相遇。

解决方案:
让fast多走1步作为补偿:fast = fast.next

现在的关系变成:

  • slow从head开始走 a 步到达环入口
  • fast从补偿位置开始走 a 步,正好也到达环入口

📐 数学验证

设链表结构:

head -> ... -> [环入口] -> ... -> [相遇点] -> ... -> [环入口]a步         b步           c步

第一阶段相遇分析:

  1. slow走了:a + b
  2. fast走了:a + 2b + c + 1 步(多了初始1步)
  3. 由于fast速度是slow的2倍:a + 2b + c + 1 = 2(a + b)
  4. 解得:c = a - 1

第二阶段补偿分析:

  1. 相遇点到环入口的实际距离:c
  2. 由于 c = a - 1,如果slow从head走 a 步,fast需要走 c + 1 = a
  3. 通过 fast = fast.next 补偿,fast走 c 步后再走1步,总共 a
  4. 两个指针在环入口完美相遇!

🎭 算法流程图

开始
链表为空或只有一个节点?
返回null
slow = head, fast = head.next
slow != fast?
fast != null && fast.next != null?
slow走1步, fast走2步
检测到环
fast = fast.next 补偿
slow = head 重置
slow != fast?
slow和fast都走1步
返回相遇点 - 环入口

🌟 算法优势

✅ 优点

  1. 逻辑直观:环检测逻辑更符合"先判断再移动"的直觉
  2. 空间最优:O(1) 空间复杂度,只使用两个指针
  3. 时间高效:O(n) 时间复杂度,每个节点最多访问常数次
  4. 数学严谨:基于严格的数学推导,通过补偿策略保证正确性

🎯 与标准Floyd算法的比较

特性标准Floyd我们的变种
初始位置都从head开始slow从head,fast从head.next
第一阶段逻辑先移动再判断先判断再移动
数学关系c = ac = a - 1
第二阶段直接开始需要补偿1步
时间复杂度O(n)O(n)
空间复杂度O(1)O(1)

🧪 示例演示

示例 1:[3,2,0,-4], pos = 1

链表结构:3 -> 2 -> 0 -> -4↑         ↓←---------←步骤分析:
a = 1, b = 2, c = 1
验证:c = a - 1 → 1 = 1 - 1 ❌等等,这里需要重新分析...
实际上:a = 1, b = 1, c = 2
验证:c = a - 1 → 2 = 1 - 1 ❌让我重新仔细分析这个例子的数学关系...

重新分析示例1:

  • 第一阶段:slow在索引1(值为2),fast在索引2(值为0)开始
  • 移动1次后:slow在索引2(值为0),fast在索引0(值为-4)后又到索引2(值为0)
  • 此时相遇!slow走了1步,fast走了3步
  • 由于是2倍速度关系:3 = 2×1 + 1(初始偏移) ✓

示例 2:[1,2], pos = 0

链表结构:1 -> 2↑    ↓←----←步骤分析:
- 第一阶段:slow在索引0(值为1),fast在索引1(值为2)
- 移动1次后:slow在索引1(值为2),fast在索引1(值为2)
- 相遇!然后进行补偿和第二阶段定位

🎪 核心洞察

💡 关键理解

  1. 偏移补偿原理:由于fast初始多走1步,破坏了标准Floyd的数学关系
  2. 补偿策略:在第二阶段开始前让fast再多走1步,恢复正确的位置关系
  3. 数学美感:通过简单的补偿操作,将"错误"的初始条件转化为"正确"的解法

🎭 编程哲学

这个算法体现了编程中的重要思想:

  • 适应性:当标准方法不直接适用时,通过巧妙的调整使其工作
  • 补偿机制:识别偏差并通过额外操作进行修正
  • 数学严谨性:确保每一步调整都有坚实的数学基础

📊 复杂度分析

⏰ 时间复杂度:O(n)

  • 第一阶段:最多遍历整个链表一次
  • 第二阶段:最多遍历 a 个节点
  • 总体:O(n),其中 n 是链表节点数

💾 空间复杂度:O(1)

  • 只使用了两个指针变量:slowfast
  • 没有使用额外的数据结构
  • 满足进阶要求的常数空间复杂度

🚀 实现技巧

🔧 关键代码片段

// 技巧1:先判断再移动的循环结构
while (slow != fast) {if (fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;} else {return null;  // 无环}
}// 技巧2:补偿策略
fast = fast.next;  // 关键的补偿步骤
slow = head;// 技巧3:同步移动直到相遇
while (slow != fast) {slow = slow.next;fast = fast.next;
}

⚠️ 常见陷阱

  1. 忘记补偿:直接进入第二阶段会导致死循环
  2. 边界处理:确保fast.next的空指针检查
  3. 理解数学关系:c = a - 1 而不是 c = a

🎓 总结与思考

🏆 算法价值

这个变种Floyd算法展示了:

  1. 创新思维:在经典算法基础上的巧妙改进
  2. 数学美学:通过补偿机制恢复算法的数学正确性
  3. 工程实用性:在不牺牲性能的前提下提供更直观的逻辑

🤔 深度思考

  • 为什么要这样改进? 使环检测的逻辑更符合直觉(先判断再移动)
  • 补偿的本质是什么? 修正由于初始条件改变而产生的数学关系偏差
  • 还有其他变种吗? 可以探索更多基于Floyd算法的创新实现

🎯 学习建议

  1. 掌握标准Floyd算法 - 理解基础原理
  2. 分析数学关系 - 深入理解为什么c = a
  3. 理解补偿策略 - 学会在算法变种中保持数学正确性
  4. 实践验证 - 通过具体例子验证算法的正确性

“在算法的世界里,创新往往来自于对经典方法的深刻理解和巧妙改进。”

这个环形链表II的解法,不仅解决了问题,更展现了算法设计中的艺术与科学的完美融合!

本算法和环形链表I的算法思想代码基本一致,可以仔细对比一下,降低记忆难度
环形链表I


public class Solution {public boolean hasCycle(ListNode head) {if (head==null||head.next==null) {return false;}ListNode slow=head,fast=head.next;while (slow != fast) {if (fast!=null&&fast.next!=null) {fast=fast.next.next;slow=slow.next;}else return false;}return true;}
}

环形链表II

public ListNode detectCycle(ListNode head) {if (head == null || head.next == null) {return null;}// 第一阶段:检测环的存在ListNode slow = head;ListNode fast = head.next;  // 关键:快指针从head.next开始while (slow != fast) {if (fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;} else {return null;  // 无环}}// 第二阶段:定位环的入口fast = fast.next;  // 关键:补偿快指针多走的那一步slow = head;       // 慢指针重置到头部while (slow != fast) {slow = slow.next;fast = fast.next;}return slow;
}
http://www.xdnf.cn/news/1415755.html

相关文章:

  • 阿里云代理商:轻量应用服务是什么?怎么用轻量应用服务器搭建个人博客?
  • Linux性能调试工具之ftrace
  • JSP 输出语法全面解析
  • 制造业生产线连贯性动作识别系统开发
  • MCP SDK 学习二
  • 【开题答辩全过程】以 基于Java的网络购物平台设计与实现为例,包含答辩的问题和答案
  • 集合-单列集合(Collection)
  • Docker中使用Compose配置现有网络
  • Ubuntu 中复制粘贴快捷键
  • LeeCode 37. 解数独
  • 【嵌入式】【电机控制】基础知识列表
  • PS自由变换
  • Dreamore AI-解读并描绘你的梦境
  • ARM裸机开发(基础汇编指令)Day02
  • 【AI智能体】LLM记账智能体+MCP服务-实现步骤与效果展示
  • 分布式锁和分布式事务
  • 使用yt-dlp下载网页视频
  • 国内大型银行数据模型实践案例
  • 2025年跨领域职业发展证书选择指南
  • 漫谈《数字图像处理》之基函数与基图像
  • Linux wlan 之网络问题定位分析 实例一
  • 面向对象中—类
  • 「数据获取」《中国工会统计年鉴》(1991-2013)(获取方式看绑定的资源)
  • EtherCAT主站IGH-- 50 -- 搭建xenomai系统及自己的IGH主站遇见的BUG
  • Android Studio 9.png制作
  • Java与分布式系统的集成与实现:从基础到应用!
  • Linex进程管理
  • Unity核心概念②:帧、生命周期函数
  • 【开题答辩全过程】以 基于微信小程序的教学辅助系统 为例,包含答辩的问题和答案
  • SAP报工与收货的区别(来自deepseek)