每日leetcode——142. 環形連結串列 II

Ethan發表於2022-03-05

題目

給定一個連結串列的頭節點 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)

雙指標

理解雙指標的思路,需要通過畫圖,計算移動距離的等量關係來理解:
image.png
在連結串列頭節點,定義兩個指標: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 三個指標。

相關文章