LeetBook乐扣题库 142. 环形链表 II

题目描述

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

示例:

复制代码
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点(值为 2 的节点)

3 → 2 → 0 → -4
    ↑          |
    └──────────┘

前置思考:

如果要完成O(1)的空间复杂度,那就要在141.环形链表的快慢指针解法的基础上优化,既然结果是具体的某一个节点,我第一时间就想着数学推导找到规律,所以问题在于怎么找到数学公式(能拿到的常量就是相遇时走了多少步,所以从步数下手)

变量定义

复制代码
head         入环点       相遇点
  |←--------- a ------------→|------------ b ------------→|
               |            |
               |←--------- c ---------------|
               (环的剩余长度)
  • a:head 到入环点的距离
  • b:入环点到快慢指针相遇点的距离
  • c:相遇点继续走回到入环点的距离
  • 环长 = b + c

前置论证:慢指针一定在走完第一圈之前与快指针相遇

在正式推导之前,需要先证明这个前提,否则步数公式会更复杂。

设定时间节点:慢指针刚踏入环入口的那一刻

  • 慢指针在环内位置 0(入口)
  • 快指针已经在环里,设其在环内位置 f,范围为 0(b+c-1)

追及分析:

  • 每走一步,快指针追近慢指针 1 格(快走2步,慢走1步,差距缩小1)
  • 快指针相对于慢指针距离为 (b+c-f) 格(因为快指针只能往前走,需要绕过来追上)
  • 因此还需要追 b+c-f

最坏情况:f=1 时,需要追 b+c-1 步,慢指针最多走 b+c-1 步。

慢指针在环内步数≤b+c−1<b+c\text{慢指针在环内步数} \leq b+c-1 < b+c慢指针在环内步数≤b+c−1<b+c

最坏情况不是f=0的原因是:慢指针刚踏入入环点时快指针也在入环点,直接就不用追了,其实是最好情况。

结论:慢指针一定在走完第一圈之前就与快指针相遇 ✅

这保证了后续推导中慢指针的总步数可以简单写成 a + b


解法:快慢指针 + 数学推导

思路

第一阶段 :用快慢指针找到相遇点。
第二阶段 :用数学公式推导出------从 head 和从相遇点同速出发的两个指针,会在入环点相遇。

数学推导

慢指针总步数: a + b(已由前置论证保证不超过一圈)

快指针总步数: 2(a + b)(始终是慢指针的两倍)

快指针路径描述: 走完入环前的路( + 在环里绕了 n 圈 + 再走 b 到达相遇点

2(a+b)=a+n(b+c)+b2(a+b) = a + n(b+c) + b2(a+b)=a+n(b+c)+b

化简:

2a+2b=a+nb+nc+b2a + 2b = a + nb + nc + b2a+2b=a+nb+nc+b

a=nb−b+nc=(n−1)b+nca = nb - b + nc = (n-1)b + nca=nb−b+nc=(n−1)b+nc

a=(n−1)(b+c)+c\boxed{a = (n-1)(b+c) + c}a=(n−1)(b+c)+c

关键解读:

  • (n-1)(b+c) 是绕环走 (n-1) 整圈,等效于原地不动
  • 所以从相遇点 (!!!注意是假设的从相遇点出发,此时相对于入环点的距离为b)出发走 a 步 = 绕了几圈后再走 c 步 = 恰好到达入环点(因为环长b+c)
  • 而入环点就是我们需要的答案,所以当A从head出发,B从相遇点出发的情况,步长为1,相遇的地方就是答案

结论:

指针 起点 走 a 步后的位置
指针 A head 入环点
指针 B 相遇点 入环点(绕圈+走c步)

两个指针从各自起点同时出发,同速行走,相遇处即为入环点!

完整代码

java 复制代码
public ListNode detectCycle(ListNode head) {
    // 第一阶段:找快慢指针相遇点
    ListNode fast = head, slow = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) {
            break;
        }
    }

    // 无环:while 正常结束(非 break 退出)
    if (fast == null || fast.next == null) {
        return null;
    }

    // 第二阶段:找入环点
    ListNode meetNode = fast;   // 从相遇点出发
    ListNode newHead = head;    // 从 head 出发
    while (meetNode != newHead) {
        meetNode = meetNode.next;
        newHead = newHead.next;
    }
    return meetNode;
}

复杂度分析

  • 时间复杂度: O(n),第一阶段最多走 O(n) 步找到相遇点,第二阶段最多走 O(n) 步找到入环点
  • 空间复杂度: O(1),只使用了有限个指针变量

延伸知识点

1. 快慢指针的适用场景

场景 用法
检测链表是否有环 快慢指针相遇则有环(LeetCode 141)
找环的入口 相遇后再用双指针(LeetCode 142)
找链表中点 快指针到尾时,慢指针在中点
找倒数第 k 个节点 快指针先走 k 步,再同速走到尾
相关推荐
Sylvia33.2 小时前
体育数据API实战:用火星数据实现NBA赛事实时比分与状态同步
java·linux·开发语言·前端·python
Coder-coco2 小时前
家政服务管理系统|基于springboot + vue家政服务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·家政服务管理系统
郝学胜-神的一滴2 小时前
贪心策略实战Leetcode 860题:柠檬水找零问题的优雅解法
数据结构·c++·算法·leetcode·职场和发展
人道领域2 小时前
Day | 07 【苍穹外卖 :用户端添加购物车】
java·开发语言·数据库·后端·苍穹外卖
不像程序员的程序媛2 小时前
springboot对于@PathVariable自动解码问题
java·前端·javascript
weixin_456321642 小时前
Java架构设计:Redis RDB持久化深度解析(原理+实战+避坑)
java·开发语言·redis
NGC_66112 小时前
CMS收集器详解
java·开发语言·jvm
毅炼2 小时前
Spring总结(2)
java·数据库·sql·spring
xuhaoyu_cpp_java2 小时前
Servlet学习
java·笔记·学习