連結串列中環的入口結點

演算法推薦管發表於2021-10-10

問題描述

給定一個連結串列,返回連結串列開始入環的第一個節點。 如果連結串列無環,則返回 null。

為了表示給定連結串列中的環,我們使用整數 pos 來表示連結串列尾連線到連結串列中的位置(索引從 0 開始)。 如果 pos 是 -1,則在該連結串列中沒有環。注意,pos 僅僅是用於標識環的情況,並不會作為引數傳遞到函式中。

說明:不允許修改給定的連結串列。

分析問題

拿到這個問題,我們最直觀的想法就是在遍歷結點的過程中去標記一下這個結點是否已經被訪問過。如果被訪問過,就代表這個結點是連結串列中環的入口點,我們直接返回就好。如果沒有被訪問過,我們就標記一下,然後接著去遍歷下一個結點,直到遍歷完整個連結串列為止。下面我們來看一下程式碼的實現。

def EntryNodeOfLoop(self, pHead):
tags = set()
while pHead:
#表示已經被訪問過了,代表有環
if pHead in tags:
return pHead
tags.add(pHead)
pHead = pHead.next
return None

我們可以看到該演算法的時間複雜度和空間複雜度都是O(n)。

優化

我們這裡也可以採用快慢指標來求解,就和上一題的解法類似,我們來看一下。

我們可以使用兩個指標fast和slow。他們都從連結串列的頭部開始出發,slow每次都走一步,即slow=slow->next,而fast每次走兩步,即fast=fast->next->next。如果連結串列中有環,則fast和slow最終會在環中相遇。

我們假設連結串列中環外的長度為a,show指標進入環後又走了b的距離與fast相遇,此時fast指標已經繞著環走了n圈。所以快指標一共走了a+n(b+c)+b=a+(n+1)b+nc的距離,我們知道快指標每次走2步,而慢指標每次走一步,所以,我們可以得出快指標走的距離是慢指標的兩倍,即a+(n+1)b+nc=2(a+b),所以a=c+(n-1)(b+c)。這裡你會發現:從相遇點到入環的距離c,再加上n-1圈的環長,恰好等於從連結串列頭部到入環點的距離。

因此,當發現slow和fast相遇時,我們再額外使用一個指標ptr指向連結串列頭部,然後它和slow指標每次都向後移動一個位置。最終,他們會在入環點相遇。

Tips: 你也許會有疑問,為什麼慢指標在第一圈沒走完就會和快指標相遇呢?我們來看一下,首先,快指標會率先進入環內。然後,當慢指標到達環的入口時,快指標在環中的某個位置,我們假設此時快指標和慢指標的距離為x,若x=0,則表示在慢指標剛入環時就相遇了。我們假設環的長度為n,如果看成快指標去追趕慢指標,那麼快指標需要追趕的距離為n-x。因為快指標每次都比慢指標多走一步,所以一共需要n-x次就能追上慢指標,在快指標遇上慢指標時,慢指標一共走了n-x步,其中x>=0,所以慢指標走的路程小於等於n,即走不完一圈就會相遇。

下面,我們來看一下程式碼實現。

def detectCycle(head):
if not head:
return None
#快慢指標
slow = head
fast = head
while True:
if not fast or not fast.next:
return None
fast=fast.next.next
slow=slow.next
#相遇時,跳出迴圈
if fast == slow:
break

ptr = head
while ptr != slow:
ptr=ptr.next
slow=slow.next
return ptr

該演算法的時間複雜度是O(n),空間複雜度是O(1)。

如果覺得不錯,歡迎大家掃碼關注。

相關文章