Description
給定一個鏈結串列的頭節點
head,判斷該鏈結串列是否存在環。如果串列中某個節點可以再次被訪問到,則存在環。
Example:
Input: head = [3,2,0,-4], pos = 1 Output: true Explanation: 串列中有一個環,尾節點連接到第 1 個節點
Intuition#
核心思路:快慢指針(Floyd’s Cycle Detection)– 如果有環,快指針終會追上慢指針。
- 如果沒有環,快指針會先到達 null
- 如果有環,快指針在環中會像跑道上的跑者一樣追上慢指針
- 每次迭代快慢指針的距離縮小 1,所以一定會相遇
Approaches#
1: HashSet#
- 概念: 將訪問過的節點存入 HashSet,如果遇到已存在的節點,說明有環
- 時間複雜度:
O(n)- 最多遍歷所有節點 - 空間複雜度:
O(n)- HashSet 儲存所有節點
class Solution {
fun hasCycle(head: ListNode?): Boolean {
val visited = HashSet<ListNode>()
var curr = head
while (curr != null) {
if (!visited.add(curr)) return true
curr = curr.next
}
return false
}
}⭐2: Floyd’s Cycle Detection (Fast & Slow Pointers)#
- 概念: 慢指針每次走 1 步,快指針每次走 2 步。若有環則兩者必會相遇;若無環則快指針先到 null
- 時間複雜度:
O(n)- 最多遍歷兩倍節點數 - 空間複雜度:
O(1)- 只用兩個指針
class Solution {
fun hasCycle(head: ListNode?): Boolean {
var slow = head
var fast = head
while (fast?.next != null) {
slow = slow?.next
fast = fast.next?.next
if (slow === fast) return true
}
return false
}
}注意使用參照相等
===而非結構相等==,因為我們要比較的是同一個節點物件,不是節點的值。
🔑 Takeaways#
- Pattern: Floyd’s Cycle Detection 是偵測環的經典演算法,也是 LeetCode 142(找環的起點)的基礎
- 關鍵技巧: 快慢指針不只能偵測環,還能找中點、找倒數第 k 個節點,是鏈結串列的瑞士刀