如何判斷一個單鏈表是否有環以及環入口

這是一個在我們學習數據結構的時候經常會遇到的問題,今天給大家帶來這個問題的幾種解法。

方法一

最容易想到的辦法就是遍歷單鏈表,如果單鏈表有環的話那麼會進入死循環,但是我們不知道單鏈表的長度,所以如果單鏈表長度很長,我們一直向下遍歷,也無法分辨出是單鏈表還沒遍歷完還是進入了死循環。

所以這種解法不靠譜。

方法二

我們可以在遍歷單鏈表中的每個元素的時候,每遍歷一個新的節點,就從頭再開始遍歷一次已經遍歷過的節點,用新節點和之前遍歷過的節點相比較,如果新節點之前遍歷過的節點相同,就說明單鏈表有環。

單鏈表

比如說已存在的單鏈表:A->B->C->D->E->F->G->D,在遍歷到第七個節點(D)的時候,再在從頭節點開始遍歷到第三個節點,因爲第三個集結點(D)和第七個節點相同,所以單鏈表一定有環。

假設從單鏈表頭節點到環入口節點的距離是D,單鏈表的環長是S。那麼算法的時間複雜度是0+1+2+3+….+(D+S-1) = (D+S-1)*(D+S)/2,可以理解成O(N*N)。

算法沒有創建額外存儲空間,空間複雜度可以簡單地理解成爲O(1)。

所以這個算法的時間複雜度是O(N*N),空間複雜度是O(1)。

找環入口的話就很簡單了,遍歷到第一個判定單鏈表有環的節點(D)就是環的入口。

方法三

考慮到方法二每遍歷一個新節點的時候都需要從頭開始遍歷遍歷一遍節點,會導致時間複雜度很高,所以我們可以把已經遍歷過的節點用一個HashSet存起來,然後每遍歷一個新節點的時候再去和HashSet中的元素做匹配,如果HashSet中已存在該節點,那麼單鏈表有環。

假設從單鏈表頭節點到環入口節點的距離是D,單鏈表的環長是S。而每一次HashSet查找元素的時間複雜度是O(1),所以總體的時間複雜度是1*(D+S)=D+S,可以簡單理解爲O(N)。

而算法的空間複雜度還是D+S-1,可以簡單地理解成O(N)。

這個算法的時間複雜度是O(N),空間複雜度是o(N).

找環入口和方法二一樣,遍歷到第一個判定單鏈表有環的節點(D)就是環的入口。

方法四

還有一種思路是創建兩個變量slow和fast同時指向頭節點,然後slow每次向後遍歷一個節點,fast每次向後遍歷兩個節點,如果單鏈表沒有環的話那麼slow將永遠追不上fast,而如果單鏈表有環的話slow就會追上fast。

單鏈表2

下面我們來模擬隨着單鏈表的遍歷slow和fast兩個變量值的變化:

次數\變量 slow fast
0 A A
1 B C
2 C E
3 D G
4 E E
5 F G
6 G E
7 D G
8 E E

單鏈表3

可以看到遍歷到第八次的時候,slow追上了fast,所以單鏈表有環。

假設從單鏈表頭節點到環入口節點的距離是D,單鏈表的環長是S。那麼循環會進行S*K次,K爲正整數,可以簡單理解爲O(N)。除了兩個指針以外,沒有使用任何額外存儲空間,所以空間複雜度是O(1)。

這個算法的時間複雜地是O(N),空間複雜度O(1)。

那麼如何找到環的入口呢?下面我們來進行一個小小的數學計算:設從單鏈表頭節點到環入口節點的距離是D,環入口到相交點的距離是X,設slow和fast第一次相遇時fast走了n圈環,slow走的距離爲len,那麼fast走的距離是2*len,可以得出下面的兩個等式:

len = D + X
2 * len = D + X + n * R

兩個等式相減可以的到:len = n * R - X

通過這個等式我們可以再定義兩個變量out和in,out指向單鏈表頭節點,in指向之前slow和fast第一次相交的節點,out和in同時向後遍歷,第一次相交的點就是環的入口。

單鏈表4

下面我們來模擬隨着單鏈表的遍歷out和in兩個變量值的變化:

次數\變量 out in
0 A E
1 B F
2 C G
3 D D

可以看到遍歷到第三次的時候,out和in相遇在D節點,所以D節點時單鏈表的環入口。


今天關於單鏈表是否有環以及環入口的問題就分享到這裏了,提供了四種解法,第一種解法不太友好也不太合理,第二中解法時間複雜度太高,第三種解法空間複雜度太高,第四種解法可能時目前的最優解法了,希望對大家有所幫助。


喜歡這篇文章的朋友,歡迎長按下圖關注公衆號lebronchen,第一時間收到更新內容。
掃碼關注

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章