面试必刷:判断链表是否有环(龟兔赛跑法,附记忆口诀)
判断链表是否存在环是算法面试的高频题,也是考察指针操作的经典问题。本文将用最通俗易懂的「龟兔赛跑」场景,帮你5分钟记住核心解法,面试时直接默写代码!
一、问题描述:什么是链表环?
链表是由节点通过next
指针连接而成的数据结构。如果链表的末尾节点指向了链表中的某个中间节点(形成一个循环),则称这个链表存在环。
例如:
1 → 2 → 3 → 4
↑ ↓
←-----←
节点4的next
指针指向了节点2,形成一个环。
二、核心解法:龟兔赛跑算法(Floyd判圈算法)
判断链表环最经典的方法是「快慢指针法」,也叫「龟兔赛跑法」。其核心思想是:
- 慢指针(乌龟):每次移动1步
- 快指针(兔子):每次移动2步
如果链表存在环,兔子会先进入环并在环内绕圈,最终追上乌龟(与乌龟相遇);如果链表无环,兔子会先到达链表末尾(遇到null
)。
场景联想:龟兔赛跑
想象链表是一条跑道:
- 初始状态:乌龟和兔子都从起点(
head
)出发。 - 比赛规则:
- 乌龟每次走1步(
slow = slow.next
) - 兔子每次跳2步(
fast = fast.next.next
)
- 乌龟每次走1步(
- 终止条件:
- 如果有环:兔子会绕回环内,追上乌龟(
slow == fast
) - 如果无环:兔子会先到达终点(
fast == null
或fast.next == null
)
- 如果有环:兔子会绕回环内,追上乌龟(
代码实现(Java)
public boolean hasCycle(ListNode head) {
// 处理空链表
if (head == null) return false;
// 龟兔同起点
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next; // 乌龟走一步
fast = fast.next.next; // 兔子跳两步
if (slow == fast) { // 兔子追上乌龟,有环
return true;
}
}
// 兔子到达终点,无环
return false;
}
三、记忆口诀:「龟兔同起点,兔跳两步赶;遇空无环套,相遇环出现」
- 龟兔同起点:
slow
和fast
都初始化为head
- 兔跳两步赶:循环中兔子每次跳两步,乌龟走一步
- 遇空无环套:如果兔子遇到
null
,说明无环 - 相遇环出现:如果兔子追上乌龟,说明有环
四、关键细节解析
为什么初始时龟兔都指向head?
- 起点相同才能保证在环内相遇。如果兔子提前出发,可能会错过与乌龟的相遇点。
循环条件为什么是
fast != null && fast.next != null
?- 确保兔子每次能跳两步。如果
fast
或fast.next
为null
,说明链表无环。
- 确保兔子每次能跳两步。如果
为什么相遇就说明有环?
- 兔子速度是乌龟的两倍。如果有环,兔子会先进入环并绕圈,最终从后面追上乌龟。
五、复杂度分析
时间复杂度:O(n)
- 无环时,兔子最多遍历n个节点
- 有环时,乌龟和兔子的相对速度为1,最多需要n步相遇
空间复杂度:O(1)
- 只使用了两个指针变量,无需额外空间
六、面试临场技巧
- 画图演示:面试时边写代码边画图,用箭头表示指针移动,清晰展示相遇过程。
- 强调边界:特别说明空链表和单节点链表的处理逻辑。
- 变体问题:如果面试官问「如何找到环的入口节点?」,可以回答:
当快慢指针相遇后,将其中一个指针重置到
head
,然后两指针以相同速度移动,再次相遇的节点即为环的入口。(原理涉及数学推导,此处不展开)
总结
判断链表环的「龟兔赛跑法」是面试必背技巧,记住口诀和场景,代码可以直接默写。这个算法不仅高效,还能延伸到其他链表问题(如找环入口、链表中点等)。面试前用示例链表手动模拟一次,确保万无一失!