返回

LeetCode 02.08:环路检测,快慢指针解题精髓

前端

链表,作为一种基础的数据结构,在程序开发中扮演着重要的角色。它由一系列节点组成,每个节点都包含数据和指向下一个节点的指针。然而,当链表中出现环路时,就会引发一系列问题,例如程序陷入死循环。LeetCode 的第 02.08 题“环路检测”正是对这个问题的考察。

如何判断一个链表是否存在环路呢?一个直观的想法是遍历链表,并将每个节点的地址存储在一个集合中。当遇到一个地址已经在集合中存在的节点时,就说明链表存在环路。这种方法简单易懂,但需要额外的存储空间来存放节点地址。有没有更节省空间的方法呢?答案是肯定的,我们可以利用快慢指针来解决这个问题,而这背后蕴含着有趣的数学原理。

想象一下,在一条环形跑道上,两个人以不同的速度跑步,速度快的人最终一定会追上速度慢的人。快慢指针的思想与此类似。我们设置两个指针,一个快指针每次移动两步,一个慢指针每次移动一步。如果链表中存在环路,那么快指针最终会追上慢指针,就像在环形跑道上跑步一样。

让我们深入分析一下其中的数学原理。假设链表的长度为 n,环路的长度为 m。当快指针走了 2n 步时,慢指针走了 n 步。如果存在环路,快指针会继续在环路中循环,速度仍然比慢指针快。当慢指针进入环路时,已经走了 n 步。此时,快指针已经在环路中绕了 k 圈,走了 2nk 步。

为了相遇,快指针领先的步数必须是环路长度 m 的倍数。也就是说,2nk - n = km。经过简单的数学变换,我们可以得到 n = (k - 1) * m。这说明,如果快慢指针相遇,那么从链表起始位置到环路入口处的距离一定是环路长度 m 的倍数。

基于这个原理,我们可以设计一个算法来检测链表中是否存在环路,并找到环路入口的位置。

算法步骤:

  1. 初始化两个指针,fast 和 slow,都指向链表的头部。
  2. 当 fast 和 slow 都不为空时,执行以下操作:
    • fast 前进两步。
    • slow 前进一步。
    • 如果 fast 和 slow 相遇,则说明链表存在环路。记录相遇点 meet。
    • 如果 fast 走到链表尾部,则说明链表不存在环路。
  3. 如果链表存在环路,将 slow 指针重置到链表头部,fast 指针仍然停留在相遇点 meet。
  4. 让 slow 和 fast 同时前进,每次都前进一步。
  5. 当 slow 和 fast 再次相遇时,相遇点就是环路的入口。

代码示例 (Python):

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

def detectCycle(head: ListNode) -> ListNode:
    if not head or not head.next:
        return None

    fast = head
    slow = head

    while fast and fast.next:
        fast = fast.next.next
        slow = slow.next
        if fast == slow:
            break

    if not fast or not fast.next:
        return None

    slow = head
    while slow != fast:
        slow = slow.next
        fast = fast.next

    return slow

常见问题解答:

  1. 为什么快指针每次移动两步,慢指针每次移动一步?
    这是为了保证快指针能够追上慢指针。如果快指针的速度不够快,就可能无法在环路中追上慢指针。

  2. 如果链表中不存在环路,算法会如何处理?
    如果链表中不存在环路,那么快指针最终会走到链表的尾部,此时算法会返回 None,表示没有找到环路。

  3. 算法的时间复杂度和空间复杂度是多少?
    算法的时间复杂度是 O(n),其中 n 是链表的长度。空间复杂度是 O(1),因为我们只使用了常数个额外的变量。

  4. 除了快慢指针,还有其他方法可以检测链表环路吗?
    是的,可以使用哈希表来存储已经访问过的节点,当遇到一个节点已经在哈希表中存在时,就说明链表存在环路。这种方法的时间复杂度也是 O(n),但空间复杂度是 O(n),因为需要额外的空间来存储哈希表。

  5. 快慢指针方法除了检测链表环路,还有什么其他应用?
    快慢指针方法还可以用来查找链表的中间节点、判断链表是否有环以及查找环的长度等。它是一种非常实用且高效的算法技巧。

LeetCode 的第 02.08 题“环路检测”是一个看似简单,实则蕴含着深刻数学原理的题目。通过学习快慢指针方法,我们可以更好地理解链表这种数据结构,并掌握一种高效的算法技巧。希望本文能够帮助读者更好地理解这道题目,并提升算法能力。