题目难度: 中等

原题链接

今天继续更新程序员面试金典系列, 大家在公众号 算法精选 里回复 面试金典 就能看到该系列当前连载的所有文章了, 记得关注哦~

题目描述

给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

示例 1:

  • 输入:head = [3,2,0,-4], pos = 1
  • 输出:tail connects to node index 1
  • 解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

  • 输入:head = [1,2], pos = 0
  • 输出:tail connects to node index 0
  • 解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

  • 输入:head = [1], pos = -1
  • 输出:no cycle
  • 解释:链表中没有环。

题目思考

  1. 你是否可以不用额外空间解决此题?

解决方案

思路

  • 一个比较容易想到的思路将节点存到集合里面, 然后如果遍历到已经在集合的节点, 就说明存在环, 但这样使用了额外空间, 如何做到不使用额外空间呢?
  • 将链表想象成一个跑道, 其中属于环的部分是圆形跑道, 不属于环的部分是直线跑道. 假设有两个人同时从跑道起点出发且速度不同, 什么情况下两人会再次相遇呢?
  • 如果跑道没有环, 那么慢的那个人永远追不上快的, 自然不会再次相遇; 但如果有环, 那么总有一个时刻两个人会在环的某个点相遇, 此时快的那个人领先若干圈
  • 所以我们可以采用快慢指针的方法, 快指针每次走两步, 慢指针每次走一步, 如果两个指针能相遇, 则说明存在环; 否则如果快指针先到达终点(即空节点), 则说明无环
  • 接下来考虑如何求环的起点:
    • 找到交点后, 设 head 到环的起点的距离为 x, 环的起点到两个指针交点的距离为 y, 交点继续往后走到环的起点的距离为 z, 慢指针达到交点时走了 m 圈完整的环, 快指针达到交点时走了 n 圈完整的环
    • 显然有 2(x+y+m(y+z)) = x+y+n(y+z), 进而得到x=(n-2m-1)(y+z) + z, 也就是说, x 相当于 z 加上若干圈完整的环的距离.
    • 所以我们可以将指针 a 重新指向链表头, 指针 b 保持在交点不变, 然后两指针每次往前走一步, 那么当 a 走了 x 步到达环路起点时, b 必定走了 z 加若干圈完整的环的距离, 恰好也到了环的起点, 也就是两指针重新出发后第一次相遇的位置, 这样就得到了题目答案

复杂度

  • 时间复杂度 O(N): 只需要遍历每个节点常数遍
  • 空间复杂度 O(1): 只使用了几个常数空间的变量

代码

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        # 经典快慢指针
        # 注意环路起点的求法:
        #   找到相等的节点后
        #   其中一个指针重新变成head
        #   然后和另一个指针每次+1
        #   直到再次相交, 该点即为环路起点
        # 证明:
        #   设head到起点为x, 起点到第一次交点为y, 第一次交点到环起点为z
        #   那么2(x+y+m(y+z)) = x+y+n(y+z) (n和m是快慢指针经过环的次数)
        #   那么x+y = (n-2m)(y+z) (n-2m>0)
        #   那么x = (n-2m-1)(y+z) + z (n-2m-1>=0)
        #   即新的头指针经过x步和原来在第一个交点的指针经过x步(可能又走了n-2m-1个环, 加上交点到起点的距离z)所处的位置一定都是环路起点
        slow, fast = head, head
        while fast:
            slow = slow.next
            fast = fast.next
            if fast:
                fast = fast.next
            if not fast:
                # 没有环
                return None
            if fast == slow:
                break
        slow = head
        while slow != fast:
            slow = slow.next
            fast = fast.next
        return slow

大家可以在下面这些地方找到我~😊

我的知乎专栏

我的头条号

我的 CSDN

我的 Leetcode

我的牛客网博客

我的公众号: 算法精选, 欢迎大家扫码关注~😊

算法精选 - 微信扫一扫关注我