「判斷單鏈表中是否存在環」是數據結構面試中的高頻經典題,曾作為微軟筆試真題。核心思路是快慢指針(Floyd 判圈算法),時間複雜度 O(N),空間複雜度 O(1),優於哈希表法的 O(N) 空間。
比喻:操場跑步
想象兩人在環形跑道上跑步:
- 慢指針 每次跨 1 步
- 快指針 每次跨 2 步
public class LinkedListCycleDetector {
/**
* 判斷單鏈表是否有環
* @param head 鏈表頭節點
* @return true 存在環,false 無環
*/
public static boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head;
// 快指針每次走兩步,需檢查 fast.next 防止空指針
while (fast != null && fast.next != null) {
slow = slow.next; // 慢指針走一步
fast = fast.next.next; // 快指針走兩步
if (slow == fast) { // 相遇,說明有環
return true;
}
}
return false; // 快指針到達 null,無環
}
// 內部節點類
static class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
}
若需定位環的入口(LeetCode 142),相遇後將任一指針重置為 head,兩指針改為同速(每次 1 步)前進,再次相遇點即為入口。數學證明:設入環前長度為 a,環長為 b,相遇時慢指針走過 s,快指針走過 2s。相遇點距入口 x,則 s = a + x,2s = a + n*b + x ⇒ s = n*b ⇒ a = n*b - x。即從 head 走 a 步,從相遇點走 (b-x) 步,同樣到達入口。
public class Main {
public static void main(String[] args) {
// 構建無環鏈表: 1->2->3->4->5
ListNode head1 = new ListNode(1);
head1.next = new ListNode(2);
head1.next.next = new ListNode(3);
head1.next.next.next = new ListNode(4);
head1.next.next.next.next = new ListNode(5);
// 構建有環鏈表: 1->2->3->4->5->3 (環入口為節點 3)
ListNode head2 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(5);
head2.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node3; // 形成環
System.out.println("無環鏈表檢測: " + hasCycle(head1)); // false
System.out.println("有環鏈表檢測: " + hasCycle(head2)); // true
}
}
快慢指針法以極致的空間效率解決環檢測問題,是鏈表類面試題的「瑞士軍刀」。掌握其「相遇必在環內」、「入口定位數學模型」兩大核心結論,舉一反三可解決「鏈表中點查找」、「兩鏈表相交判斷」等變體問題。