題目
給定一個連結串列的頭節點 head ,返回連結串列開始入環的第一個節點。 如果連結串列無環,則返回 null。
如果連結串列中有某個節點,可以通過連續跟蹤 next 指標再次到達,則連結串列中存在環。 為了表示給定連結串列中的環,評測系統內部使用整數 pos 來表示連結串列尾連線到連結串列中的位置(索引從 0 開始)。如果 pos 是 -1,則在該連結串列中沒有環。注意:pos 不作為引數進行傳遞,僅僅是為了標識連結串列的實際情況。
輸入:head = [3,2,0,-4], pos = 1
輸出:返回索引為 1 的連結串列節點
解釋:連結串列中有一個環,其尾部連線到第二個節點。
輸入:head = [1], pos = -1
輸出:返回 null
解釋:連結串列中沒有環。
遍歷+雜湊
最簡單的辦法就是遍歷每個節點,同時把節點存到一個字典中,當遍歷下一個節點時,看看該節點是否已經存在於字典中,如果是,就說明該節點是環入口節點
def detectCycle(head: ListNode) -> ListNode:
hashTable = {}
while head:
if head in hashTable:
return head
hashTable[head] = 1
head = head.next
return None
時間複雜度:O(n)
空間複雜度:O(n)
雙指標
理解雙指標的思路,需要通過畫圖,計算移動距離的等量關係來理解:
在連結串列頭節點,定義兩個指標:fast和slow
fast每次移動2個節點,slow每次移動1個節點
如果連結串列沒有環,fast指標會先移動到null處
如果連結串列有環,fast先進入環,slow隨後進入環,最終他們肯定會在環中的某個節點相遇
假設fast和slow第一次相遇時的節點如圖所示
a代表連結串列頭節點到環的入口節點的節點數
b代表環的入口節點到fast和slow第一次相遇的節點的節點數
c代表第一次相遇節點到環的入口節點的節點數
此時,fast走過的節點數為 a+b+n(c+b) ,n表示fast在環中繞的圈數
slow走過的節點數為 a+b
fast和slow是一同移動的,所以任意時刻他們的移動步數是相同的,但是fast每次移動2個節點,相同的步數下,fast移動的節點數應該是slow的2倍
所以 a+b+n(c+b) = 2(a+b)
整理等式得:a=n(b+c)-b = nb+nc-b = (n-1)b+nc = (n-1)b+(n-1)c+c=(n-1)(b+c)+c
得 a = (n-1)(b+c)+c
也就是說:頭節點到環入口節點的距離 = 第一次相遇節點到環入口節點的距離+(n-1)圈環的長度
如果此時有兩個指標a和b,a從頭節點開始移動,b從slow和fast第一次相遇節點開始移動,均每次移動一個節點,a和b最終會在環入口節點處相遇。
此時,slow已經處在第一次相遇節點位置,因此當slow和fast第一次相遇時,再在頭節點處定義一個指標,和slow一起每次移動1個節點,他們相遇的那個節點就是環入口節點。
def detectCycle(self, head: ListNode) -> ListNode:
# 排除空連結串列情況
if not head:
return None
fast,slow,ptr = head,head,head
while fast:
# 排除連結串列只有一個節點的情況
if not fast.next:
return None
# 移動fast和slow
slow = slow.next
fast = fast.next.next
# fast和slow相遇時
if slow == fast:
# ptr和slow開始移動,直到相遇
while ptr != slow:
slow = slow.next
ptr = ptr.next
# 相遇時,返回該節點,就是入口節點
return ptr
return None
# 跟簡潔優雅版
def detectCycle(self, head: ListNode) -> ListNode:
fast, slow = head, head
while True:
if not (fast and fast.next): return
fast, slow = fast.next.next, slow.next
if fast == slow: break
fast = head
while fast != slow:
fast, slow = fast.next, slow.next
return fast
時間複雜度:O(N),其中 N 為連結串列中節點的數目。在最初判斷快慢指標是否相遇時,slow 指標走過的距離不會超過連結串列的總長度;隨後尋找入環點時,走過的距離也不會超過連結串列的總長度。因此,總的執行時間為 O(N)+O(N)=O(N)。
空間複雜度:O(1)。我們只使用了slow,fast,ptr 三個指標。