今天在微信上看到一篇介紹如何判斷單向鏈表是否有環的文章,感覺很有意思,整理一下讀后的思路。
一、判斷單向鏈表是否有環
方法1:設置一個Hashset,順序讀取鏈表中的節點,判斷Hashset中是否有該節點的唯一標識(ID)。如果在Hashset中,說明有環;如果不在Hashset中,將節點的ID存入Hashset。
這種方法時間復雜度已經最優,但是因為額外申請了Hashset,所以空間復雜度不算最優。
方法2:設置2個指針,指向頭節點。第1個指針每次指向下一個節點;第2個指針指向下一個節點的下一個節點(可以大致理解為,第1個指針每次加1,第2個指針每次加2)。如果第2個指針指向null,說明到達鏈表結尾,鏈表中沒有環;如果鏈表中有環,一定會在環上的某個節點處,兩個指針同時指向同一個節點。這就是追擊相遇的問題。
文章的作者提到了為什么兩個指針一定會在環上相遇,做了一個類比,運動員沿跑道跑步,一個快一個慢,二人無限循環,兩人一定在某個位置再次相遇,就是所謂的扣圈。但是個人覺得這只是一個類比,不能真正的證明有環鏈表同樣適用。畢竟人跑步是一個連續的問題,而鏈表的追擊問題是一個離散的問題。
我的理解,假設在慢指針進入環的時候,快指針到慢指針的距離為n;那么下一次移動的時候,快指針到慢指針的距離將縮小為(n-1);再下次,為(n-2);即每次移動,距離縮小1,如此往復,兩個指針的距離最終將變成2,進而變成1,最終變成0。而且從慢指針進入環開始計算,到二者相遇,一定經過了n次的移動。
二、求環的長度
因為有上面的結論(從慢指針進入環開始計算,到二者相遇,一定經過了n次的移動)。相似的,可以得到如下結論,假設環長為R,從二者在環上第一次相遇,到二者在環上第二次相遇,一定經過了R次移動。
因此,只要記錄二者從第一次相遇到第二次相遇一共移動了多少步,就知道環長了。
三、求環的起點的位置
假設起點到環起點的長度為LenA;開始到第一次相遇,移動了t步;環的長度為R;環的起點到二者第一次相遇位置的距離為x,二者相遇的時候快指針已經在環上轉了n圈。
則有如下等式成立: 慢指針移動的距離=LenA + x; 即 t = LenA + x;
快指針移動的距離=LenA + n * R + x; 即 2t = LenA + n * R + x;
所以 2*(LenA+x) = LenA + n*R + x;
即 LenA = n * R - x;
上面的等式可以這樣理解,如果慢指針從鏈表起點開始移動,快指針從環起點開始移動,當慢指針從起點走到環起點的時候,快指針一直在環上移動,而且還差x步就移動了n圈。進而可以得到結論,如果慢指針從鏈表起點開始移動,快指針從二者第一次相遇點開始移動,當慢指針到達環起點的時候,快指針也正好達到環起點,即二者指向相同的節點。
可以參考圖片(從另一篇博客黏貼,博客地址:求有環單鏈表中的環長、環起點、鏈表長):

四、求鏈表長
有了二中的環長R和三中的LenA,二者相加就是鏈表長。
五、無聊的補充
后來坐車的時候想到這樣一個東西,可能沒什么用途,先記錄下來。當慢指針到達環起點時,假設這時快指針在PosB處,PosB距離環起點的距離也是x。
