链表

快慢指针:环检测

快慢指针(也叫 Floyd 龟兔赛跑)用一快一慢两个指针,在链表上跑一遍就能判断有没有环。如果有环,两个指针一定会在环里某个节点相遇

为什么一快一慢必定相遇

把环想象成一个跑道:

  • slow 每步走 1 格
  • fast 每步走 2 格

如果链表无环,fast 会先走到 nullptr —— 没相遇就退出。

如果链表有环,fast 会先进入环。一旦两个指针都在环里,每走一步它们的距离就缩小 1(因为相对速度是 1 格/步)。距离最多是环长 - 1,所以最多走环长次就追上 —— 必然相遇

代码

bool hasCycle(Node* head) {
  Node *slow = head, *fast = head;
  while (fast != nullptr && fast->next != nullptr) {
    slow = slow->next;
    fast = fast->next->next;
    if (slow == fast) return true;
  }
  return false;
}

注意循环条件 fast != nullptr && fast->next != nullptr —— 因为 fast->next->next 要两步都存在才能走。

复杂度

  • 时间:O(n) — 没有环时 fast 最多走 n/2 步;有环时最多走 n 步(追上)
  • 空间:O(1) — 只用两个指针,不需要额外存储

如果用哈希表记录访问过的节点也能判环,但空间是 O(n),败给快慢指针。

应用

快慢指针的套路还能解决:

  • 找链表中点(fast 走完时 slow 正好在中间)
  • 找倒数第 k 个节点(fast 先走 k 步,再一起走)
  • 找环的入口(相遇后 slow 回头、fast 留下,再各走 1 步,再次相遇就是入口)

在动画里看什么

  • slow 指针(浅蓝标签)一格一格走
  • fast 指针(深蓝标签)两格两格走
  • 末尾若有暖红色弧线,说明链表末尾连回某个前面的节点,形成了环
  • 两个指针同时停在一个节点时,节点变成暖红色 —— 追上了