返回

以蜗牛的速度,在单链表的赛道上一决胜负:识别环形链表并寻找入口节点

闲谈

蜗牛的速度:慢指针与快指针

当我们面对一个未知的单链表,想要判断它是否有环时,可以使用一种巧妙而高效的算法——弗洛伊德循环查找算法。该算法利用了两个指针,慢指针和快指针,以不同的速度在链表中移动。慢指针每次移动一步,而快指针每次移动两步。如果链表中有环,那么这两个指针终将相遇。

这种蜗牛般的移动方式是算法的关键所在。慢指针的稳重与快指针的激进形成了鲜明的对比,却巧妙地为我们揭示了环形链表的秘密。如果链表中没有环,那么快指针将永远比慢指针走得更远,最终到达链表的末端。此时,它们相遇了,但不是在环中相遇,而是在链表的终点。

然而,如果链表中存在环,那么快指针在环中绕圈的速度是慢指针的两倍。这意味着快指针最终会追上慢指针,并在环中相遇。当它们相遇时,它们并不在链表的末端,而是处在环路之中。这正是环形链表的标志。

寻找入口节点:关键的一步

当我们确定链表中有环后,下一步就是找到环的入口节点,即第一个进入环中的节点。这听起来像是一个棘手的问题,但实际上,它与检测环形链表一样,只需要一些巧妙的思考和步骤。

首先,我们将慢指针留在相遇点,然后将快指针重置到链表的头部。现在,我们以相同的速度移动这两个指针,每次移动一步。这一次,当它们再次相遇时,相遇点就是环的入口节点。

为什么这个方法有效?让我们从环的入口节点出发,一步一步地分析指针的移动。慢指针从相遇点开始,每次移动一步,而快指针从链表头部开始,也每次移动一步。当快指针到达环的入口节点时,慢指针也恰好到达了环的入口节点。因此,当它们相遇时,相遇点就是环的入口节点。

代码示例:让算法动起来

为了让您更直观地理解算法,我们提供了一个代码示例,以便您在实践中亲眼见证算法的运作。

def has_cycle(head):
  """
  检测单链表是否有环。

  参数:
    head: 链表的头节点。

  返回:
    True 如果链表有环,否则返回 False。
  """

  slow = head
  fast = head

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

    if slow == fast:
      return True

  return False


def find_cycle_entrance(head):
  """
  找到环形链表的入口节点。

  参数:
    head: 链表的头节点。

  返回:
    环形链表的入口节点,如果链表没有环,则返回 None。
  """

  # 首先检测链表是否有环。
  if not has_cycle(head):
    return None

  # 将慢指针留在相遇点,将快指针重置到链表头部。
  slow = head
  fast = head

  # 以相同的速度移动两个指针,直到它们再次相遇。
  while slow != fast:
    slow = slow.next
    fast = fast.next

  # 相遇点就是环的入口节点。
  return slow

结语

在单链表的赛道上,我们以蜗牛的速度,利用弗洛伊德循环查找算法,成功识别了环形链表并找到了它的入口节点,同时保持了空间复杂度为O(1)。希望这篇文章能为您揭开单链表环形结构的神秘面纱,让您在面对环形链表时能够从容应对,游刃有余。