LeetCode-寻找环形链表的入口

【LeetCode 刷题】寻找环形链表的入口节点:快慢指针 + 数学推导(O (1) 空间)

在上一篇「判断链表是否有环」的基础上,本题是进阶考点 ------不仅要判断链表是否存在环,还要找到环的第一个入口节点。常规解法(哈希表存储节点)会占用 O (n) 空间,而本文分享的「快慢指针 + 数学推导」解法,能在 O (n) 时间、O (1) 空间内精准找到环入口,是面试中最亮眼的最优解。

一、题目描述

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

核心要求

  • 不允许修改给定的链表;
  • 空间复杂度要求 O (1)(禁止使用哈希表等额外存储);
  • 环的定义:链表中某个节点的 next 指针指向链表中之前出现过的节点,形成环形结构。

链表节点定义

复制代码
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */

二、解题思路:快慢指针 + 数学推导(核心)

1. 两步核心逻辑

本题解法分为两个关键阶段,缺一不可:

阶段 1:快慢指针找「相遇点」

先用「快指针(步长 2)、慢指针(步长 1)」遍历链表:

  • 若链表无环:快指针会先走到末尾(null),直接返回 null
  • 若链表有环:快慢指针会在环内某个节点相遇(这是找到入口的前提)。
阶段 2:双指针找「环入口」

在快慢指针相遇后,新增两个指针:

  • index1:从链表头节点出发,每次走 1 步;
  • index2:从快慢指针相遇点出发,每次走 1 步;两个指针相遇时,指向的节点就是环的入口节点(数学推导证明这一结论)。

2. 关键数学推导(面试高频考点)

要理解「为什么双指针能找到入口」,必须掌握以下推导(通俗版):

定义变量
  • a:链表头节点 → 环入口节点的距离;
  • b:环入口节点 → 快慢指针相遇点的距离;
  • c:相遇点 → 环入口节点的距离;
  • L:环的总长度,L = b + c
  • n:快指针在环内绕的圈数(n ≥ 1)。
推导过程
  • 慢指针走的总路程:a + b(未绕环,只走了链表头到相遇点);
  • 快指针走的总路程:a + b + n×L(走了 a+b 后,绕环 n 圈才和慢指针相遇);
  • 因为快指针速度是慢指针的 2 倍,所以:2×(a + b) = a + b + n×L化简得:a = n×L - b又因为 L = b + c,代入得:a = (n-1)×L + c
结论

a = (n-1)×L + c 的核心含义:

  • 从「链表头到环入口」的距离 a,等于从「相遇点绕环 (n-1) 圈后再走 c 的距离」;
  • 因此,index1(走 a)和 index2(走 (n-1)×L + c)最终会在环入口相遇((n-1)×L 是绕环整数圈,不影响相遇结果)。

3. 思路可视化(以 a=2, b=1, c=2, L=3 为例)

plaintext

复制代码
链表结构:1 -> 2(入口)-> 3 -> 4 -> 2(环)
- a=2:头节点1 → 入口2的距离;
- b=1:入口2 → 相遇点3的距离;
- c=2:相遇点3 → 入口2的距离;
- L=3:环(2→3→4→2)的长度。

阶段1:快慢指针相遇
- 慢指针:1→2→3(路程a+b=3);
- 快指针:1→2→3→4→2→3(路程a+b+1×L=3+3=6,是慢指针2倍);
- 相遇点为3。

阶段2:双指针找入口
- index1:1→2(走a=2步);
- index2:3→4→2(走c=2步,绕环0圈);
- 相遇点为2(环入口)。

三、完整代码实现

复制代码
public class Solution {
    public ListNode detectCycle(ListNode head) {
        // 初始化快慢指针,均指向链表头节点
        ListNode fast = head;
        ListNode slow = head;
        
        // 阶段1:快慢指针找相遇点(无环则直接返回null)
        while (fast != null && fast.next != null) {
            // 快指针走2步,慢指针走1步
            fast = fast.next.next;
            slow = slow.next;
            
            // 快慢指针相遇,进入阶段2:找环入口
            if (fast == slow) {
                // index1从链表头出发,index2从相遇点出发
                ListNode index1 = head;
                ListNode index2 = fast;
                
                // 双指针每次走1步,相遇时即为环入口
                while (index1 != index2) {
                    index1 = index1.next;
                    index2 = index2.next;
                }
                // 返回环入口节点
                return index1;
            }
        }
        
        // 循环结束(快指针到末尾),说明无环
        return null;
    }
}
相关推荐
飞Link2 小时前
耳机连接电脑时调节耳机音量电脑音量也会随着改变
算法·电脑
此方ls2 小时前
机器学习聚类算法一——K均值
算法·机器学习·聚类
再难也得平2 小时前
力扣73. 矩阵置零(Java解法)
算法·leetcode·矩阵
进击切图仔2 小时前
生成 .so 和使用 .so
java·javascript·算法
样例过了就是过了2 小时前
LeetCode热题100 岛屿数量
数据结构·c++·算法·leetcode·dfs
重生之后端学习2 小时前
300. 最长递增子序列
数据结构·算法·leetcode·职场和发展·排序算法·深度优先
CoovallyAIHub2 小时前
国产小龙虾方案实战:nanobot + 通义千问,钉钉上随时派活
深度学习·算法·计算机视觉
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章32-圆环卷收
图像处理·人工智能·opencv·算法·计算机视觉
OYangxf2 小时前
【力扣hot100】哈希专题
算法·leetcode·哈希算法