判斷單鏈表中是否有環找到環的入口節點


 

判斷單鏈表中是否有環,找到環的入口節點

聲明

  1. 文章可以隨意轉載,但請注明出處。
  2. 文中有一些地方引用了其他文章,但都已標明出處。如有侵犯,可立即刪除。
  3. 文中有些地方並無冒犯之意,希望提及的博客作者理解。沒有你們的幫助,對這個問題毫無頭緒。
  4. 由於CSDN博客系統的內部錯誤,所有的公式后面都有一條惱人的豎線,實屬無奈。
  5. 歡迎評論。

文章梗概

本文通過對現有資料的收集和整理,給出了一種相對簡單的嚴格證明的“判斷單鏈表是否有環,找到環的入口節點”的方法。

題目描述

一個鏈表中包含環,請找出該鏈表的環的入口結點(牛客網題目鏈接),題目中沒有說是單鏈表,從給出的代碼中可以看出是單鏈表而且不可修改鏈表元素的定義。

思考過程

從兩個大的角度思考這個問題

1.記錄遇到的每一個鏈表元素

在一次遍歷過程中的,使用一種數據結構(數組、Hash表、基數樹)記錄遇到的每一個鏈表元素並判斷是否已經遇到過,其中使用基數樹可以獲得O(n)

的時間復雜度,但是可會有比較高的空間復雜度。

2.利用鏈表的性質

  • 在題目的討論頁看到了比較有想法的一個答案至_的答案,正如其所言時間復雜度為O(n),兩個指針,一個在前面,另一個緊鄰着這個指針,在后面。兩個指針同時向前移動,每移動一次,前面的指針的next指向NULL。也就是說:訪問過的節點都斷開,最后到達的那個節點一定是尾節點的下一個,也就是循環的第一個。這時候已經是第二次訪問循環的第一節點了,第一次訪問的時候我們已經讓它指向了NULL,所以到這結束。這樣做的話過題目是可以了,但是會破壞掉原來的鏈表,所以並不是一個特別完美的解決辦法。如果可以在鏈表元素中加入一個記錄“邏輯斷開”的元素,也就是說在遍歷的過程中,不真正的斷開元素之間的連接,而是使用一個記錄值,記錄下“邏輯上的斷開”
  • 另一個答案頁上比較正統的回答為0909的回答,其主要思想和蒙恩的罪人的新浪博客大同小異,優雅簡潔,后面主要針對蒙恩的罪人的新浪博客進行討論。

相關問題的解法與證明

給定一個鏈表,只給出頭指針h


1.如何判斷是否存在環?

使用追趕的方法,設定兩個指針slowfast

,均從頭指針開始,每次分別前進1步、2步。如存在環,則兩者相遇;如不存在環,fast遇到NULL

退出。其中主要的思想就是“環形相遇追及問題”,理解上應該不復雜。

2.如何知道環的長度?

記錄下問題1的相遇點,slowfast

從該點開始,再次相遇時slow所經過的節點數就是環的長度。從環上的任意一點開始,slowfast再次相遇時slow經過的節點數就是環的長度,因為此時slowfast起始距離為環長,速度差為1

。選擇問題1的相遇點為起始點是為了確保起始點為環上的一點。

3.如何找出環的連接點在哪里?

設問題1中的相遇點為m1

,賦值p=m1,q=h,其中h為鏈表頭結點,然后p,q每次1步向前運動,p,q

再次相遇所在的位置就是環的入口節點(環的連接點)。這里和上面提到的博客中的敘述差別非常大,這也是其有些問題的地方,我在這里更正了其說法,並給出了相對嚴格的證明。

相對簡潔的實現

public class Solution { ListNode EntryNodeOfLoop(ListNode h){ if(h == null || h.next == null) return null; ListNode slow = h; ListNode fast = h; while(fast != null && fast.next != null ){ slow = slow.next; fast = fast.next.next; if(slow == fast){ ListNode p=h; ListNode q=slow;//相當於讓q指向了m1 while(p != q){ p = p.next; q = q.next; } if(p == q) return q; } } return null; }

 

代碼及問題三的證明

我們把該鏈表抽象為這樣一個模型,假設環長為n

情景1
此處輸入圖片的描述
此圖表示其實狀態,其中h

表示鏈表頭節點,slow,fast,起始狀態指向ht

表示環的入口節點。

情景2
此處輸入圖片的描述
此圖表示slow

運動到了tfast運動到m1,節點h和節點t之間的距離為a,節點t和節點m1之間的距離(弧長)為b,並設此時fast在環上做了r次圓周運動(因為an的長度都不固定,多以fast可能已經在環上運動了好多圈了)。相對於slow其運動的距離為:a由於fast速度是slow的二倍,所以其運動的距離(步數)為:2a
並且,經過觀察可知fast運動的距離為:a+nr+b

,所以可知公式①:

 
a=nr+b

 

情景3.
此處輸入圖片的描述
此時,slow,fast

相遇在節點m2,也就是代碼中10行判斷成立的地方。其中m2為相遇點,b還是為弧長。由於鏈表的指針是有方向的,我們約定在環上計算距離的時候按照逆時針計算,也就是說,從tm1的距離為b,從m1t的距離為nb(其中n為環的長度)。
同理在情況2中,從fastslow的距離為nb,它們的速度差為1,所以它們再次相遇的時候經過的時間為nb1=nb,slow經過的距離為(nb)×1=nb,所以假設相遇點為m2,那么顯而m2t的距離為b

情景4.
此處輸入圖片的描述
情況4對應着代碼中的11

~19行。因為通過上面的討論,如果能讓q向前運動:b+xn步,那么q的位置恰好是t,其中x{0,1,2,3,}
值得高興的是,在情況2中我們有公式①,觀察到a恰好符合這樣一個步數值,所以我們讓p=hp,q,都每次向前移動1,當他們相遇的時候恰好就是環的入點t,也就是說ph移動到pq再次相遇在這里的作用是提供一個計數。
所以,當pq再次相遇的時候,他們的相遇點恰好了t

,也就是需要找的環的入口點。

復雜度

我們關注第一次循環的slow

和第二次循環的p,因為它們都是每次前進一步,由它們移動的步數,可以得到算法的時間復雜度,所以易知時間復雜度為O(n),空間復雜度為O(1)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM