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 步,再同速走到尾
相关推荐
云烟成雨TD11 小时前
Spring AI Alibaba 1.x 系列【6】ReactAgent 同步执行 & 流式执行
java·人工智能·spring
于慨11 小时前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
swg32132111 小时前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
gelald11 小时前
SpringBoot - 自动配置原理
java·spring boot·后端
殷紫川11 小时前
深入理解 AQS:从架构到实现,解锁 Java 并发编程的核心密钥
java
‎ദ്ദിᵔ.˛.ᵔ₎11 小时前
LIST 的相关知识
数据结构·list
一轮弯弯的明月11 小时前
贝尔数求集合划分方案总数
java·笔记·蓝桥杯·学习心得
chenjingming66611 小时前
jmeter线程组设置以及串行和并行设置
java·开发语言·jmeter
殷紫川11 小时前
深入拆解 Java volatile:从内存屏障到无锁编程的实战指南
java
eddieHoo11 小时前
查看 Tomcat 的堆内存参数
java·tomcat