链表题解——环形链表 II【LeetCode】

142. 环形链表 II

快慢指针法

一、算法逻辑(每一步通顺讲解)

1️⃣ 初始化两个指针 slow 和 fast,均指向头节点

  • slow 每次走一步,fast 每次走两步;

  • 目标是通过“追及问题”判断链表是否存在环。

2️⃣ 快慢指针开始移动,进入 while 循环

  • 如果 fast 变成 Nonefast.nextNone,说明链表无环(会提前退出);

  • 否则,继续让 slow 走一步、fast 走两步;

  • 一旦 slow == fast,说明快慢指针在环中相遇。

3️⃣ 相遇后,引入新指针 ptr = head,准备寻找环的入口

  • 此时 slowfast 在环中某处;

  • ptr 从头节点开始走,每次走一步;

  • 同时让 slow 继续走,每次也是一步;

  • 两者第一次相遇的地方,就是环的入口节点,返回它即可。

4️⃣ 如果整个过程中 fastfast.nextNone,说明链表无环,返回 None


二、核心点

1. 快慢指针相遇代表链表有环;
2. 从相遇点和头节点分别出发,再次相遇点就是环的入口。

这个结论的数学推导是算法的核心,简要解释如下:

  • 设链表头到环入口的长度为 a,环的长度为 b

  • 相遇时,slow 走了 a + x 步,fast 走了 a + x + nb(n是环内转的圈数);

  • 又因为 fast 是 slow 的两倍速度:
    2(a + x) = a + x + nb → a = nb - x

  • 所以从相遇点走 a 步,刚好能回到环入口;

  • 同理,从头节点走 a 步也刚好到环入口;

  • 因此:从 headslow 同时一步步走,第一次相遇的位置就是环的入口。

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
    slow = fast = head  # 初始化慢指针和快指针,都指向链表头节点
    while fast is not None:  # 当快指针不为空时
        slow = slow.next  # 慢指针每次移动一步
        if fast.next is None:  # 如果快指针的下一个节点为空
            return None  # 说明链表没有环,返回 None
        fast = fast.next.next  # 快指针每次移动两步
        if fast == slow:  # 如果快慢指针相遇
            ptr = head  # 初始化一个新指针,指向链表头节点
            while ptr != slow:  # 当新指针和慢指针不相遇时
                ptr = ptr.next  # 新指针每次移动一步
                slow = slow.next  # 慢指针每次移动一步
            return ptr  # 返回新指针指向的节点(即环的入口节点)
    return None  # 遍历结束,没有发现环,返回 None

上面思路和图解来自灵茶山艾府

三、时间复杂度分析

  • 第一次相遇: 快慢指针最多走 O(n) 步(n 是链表长度);

  • 寻找入口: 再走一段最多也是 O(n)

  • 所以总时间复杂度为:

时间复杂度:O(n)


四、空间复杂度分析

  • 使用了几个指针变量,但没有使用额外的数据结构;

  • 所以空间复杂度为:

空间复杂度:O(1)


✅ 总结一句话

该算法通过快慢指针的相遇与数学性质,在线性时间和常数空间下找出链表环的入口节点,核心在于“环形追赶+数学推导”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值