在資料結構與演算法面試中,「判斷單鏈表是否有環」與「找到環的入口節點」是經典必考題目。本文將系統梳理三種主流解法:雙指針法(Floyd 判圈演算法)、哈希集合法與暴力雙重循環法,並附上 C++ 完整實現碼,幫助你徹底掌握核心邏輯與時間空間複雜度權衡。
給定單鏈表頭指針 head,判斷鏈表中是否存在環。若存在,返回環的入口節點(即第一個進入環的節點);否則返回 NULL。

findFirstCycleNode思路:外層指針 pCur 遍歷每個節點,內層指針 pStart 從頭開始尋找是否能遇到 pCur。若 pStart 走到 pCur 時步數相等,說明 pCur 不在環中;若步數不等,則 pStart 為入口節點。
findFirstCycleNode2思路:遍歷鏈表,將訪問過的節點地址存入 std::set<Linknode*>。若發現當前節點已在集合中,即為入口節點。
思路:
fast 每次走兩步,慢指針 slow 每次走一步。若相遇,證明有環。head,兩指針改為同速(每次一步)前進,再次相遇處即為入口節點。數學證明:設頭節點到入口距離為 a,入口到相遇點距離為 b,環長為 L。相遇時 slow 走 a+b,fast 走 2(a+b),差值 a+b = kL ⇒ a = kL - b。因此從頭出發與從相遇點出發同速走 a 步必在入口相遇。
struct Linknode {
int data;
Linknode* next;
Linknode(int val) : data(val), next(nullptr) {}
};
// 建立帶環鏈表:最後一個節點指向 head->next(即第 2 個節點)
Linknode* createCycleList(int n) {
if (n <= 1) return nullptr;
Linknode* head = new Linknode(1);
Linknode* cur = head;
for (int i = 2; i <= n; ++i) {
cur->next = new Linknode(i);
cur = cur->next;
}
cur->next = head->next; // 形成環,入口為第 2 個節點
return head;
}
Linknode* detectCycleEntry(Linknode* head) {
if (!head || !head->next) return nullptr;
Linknode *slow = head, *fast = head;
// 階段 1:判斷是否有環
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) break; // 相遇
}
if (!fast || !fast->next) return nullptr; // 無環
// 階段 2:定位入口
slow = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow; // 入口節點
}
#include <unordered_set>
Linknode* detectCycleEntryHash(Linknode* head) {
std::unordered_set<Linknode*> visited;
while (head) {
if (visited.count(head)) return head;
visited.insert(head);
head = head->next;
}
return nullptr;
}
int main() {
Linknode* head = createCycleList(10); // 10 節點,入口為節點 2
Linknode* entry = detectCycleEntry(head);
if (entry)
std::cout << "環入口節點值:" << entry->data << std::endl; // 輸出 2
else
std::cout << "無環" << std::endl;
return 0;
}
| 方法 | 時間 | 空間 | 適用場景 |
|---|---|---|---|
| 暴力雙重循環 | O(n²) | O(1) | 教學、極小數據 |
| 哈希集合 | O(n) | O(n) | 業務代碼、快速原型 |
| 快慢指針 | O(n) | O(1) | 面試、嵌入式、大數據 |
面試加分點:能手寫快慢指針代碼、推導數學證明、說明為什麼重置後同速相遇必在入口、討論「環長度」與「入口距離」的延伸問題。