1. 程式人生 > >為什麼用快慢指標檢測連結串列是否有環的時候,快指標的步長選擇的是2,而不是3,4,5?

為什麼用快慢指標檢測連結串列是否有環的時候,快指標的步長選擇的是2,而不是3,4,5?

上個月在eBay上海進行了二面,一共4輪,每輪45分鐘左右。感覺基礎的資料結構,演算法,Java語言的題目回答得還行,對於一些開放的Problem Solving型別的問題有點兒束手無策。自然,最後的結果也是被鄙視了。有些失落,不過最讓我失落的不是沒有回答好那些開放的題,而是被問到如標題所述的問題時,腦子一片空白。

在準備面試的過程中,就會發現,利用快慢指標檢測單向連結串列中是否存在環的答案遍地都是。而這些答案中幾乎一致地默認了設定快指標的步長為2。然後我們就記住了這些答案,信心滿滿地準備迎接面試。當然,大部分公司也就看你知不知道怎麼檢測出環,再深入一點兒就問你怎麼確定環的起始位置。但是再深入一點兒呢?為什麼快指標的步長非得是2,而不能是3,4,5,或者其他的呢?

這篇部落格就是解釋為什麼步長選擇的是2,同時提醒我自己知其然,而不知其所以然的代價。

問題:

  1. 給你一個單向連結串列,實現一個演算法,判斷這個連結串列中是否存在環;
  2. 如果存在環,返回環的起始結點;
  3. (如果這個時候你給出的實現是用的快慢指標,而且步長是2)步長選擇3,4,5,或者其它的可以解決以上兩個問題嗎?如果也可以,那麼為什麼你選擇的步長是2?

證明快慢指標在環中是可以相遇的:

證明這個問題?可能你會說,只要快慢兩個指標都進入環中,兩者有速度差,快指標就一定能夠追上慢指標。可是這兒“追上”有可能是兩種情況(注意,這兒討論的是一般情況,快指標步長k >= 2),一是恰好追上(也就是相遇了),二是追上並超過了慢指標。當然,這兩種情況都不妨礙我們判斷是否有環存在。但是對於問題2,網上給出的解法通常都以恰好相遇為前提,求出起始結點的(至於怎麼求,稍後會講到)。那既然這樣,我們就有必要證明無論快指標步長k為多少,快慢指標在環中都能恰好相遇。 證明開始: 首先,換個角度想問題。用一個步長為1的指標模擬遍歷這個單向連結串列的過程。如上圖所示,設單向連結串列的起始結點為X0(0是下標,可惜CSDN還不支援上下標輸入),環的起始結點為Xs。自然,遍歷到最後就是一遍遍的重複這個環。如果,我們把遍歷的項都不斷記錄到一個數組中的話(下標按次序遞增),最後就是一定長度的序列串的重複。例如, X0, X1, ... , Xs,      Xs+1,       ..., Xs+cl-1     Xs+cl, Xs+cl+1, ..., Xs+2*cl-1     ... cl (circle length)表示環的長度。 假設 j 是 cl 的整數倍,並且是 cl 整數倍中滿足 j > s 最小的那個數。對於任意的 k (k >= 2),我們考慮下標分別為 j 和 jk 的兩個位置 Xj 和 Xjk。可見,Xj 就是慢指標走了 j 步之後的位置,而 j > s 則保證了此時慢指標已經進入環中。同理,此時的快指標在位置 Xjk,必然也在環中。 因為 j 是 cl 的整數倍,我們可以把 Xjk 看成是一個指標從位置 Xj 開始,走了 (k - 1) 次的 j 步。而每走 j 步,在單向連結串列的環中,其實又回到了 Xj 位置,因為 j 是 cl 的整數倍。所以我們有,Xj = Xjk 。這樣,我們就證明了快慢指標肯定會恰好相遇的問題。

為什麼快指標步長要為2呢?

有了上面證明的基礎,就不難發現 k 越小,所需的遍歷次數就越少,因為給定一個帶環的單向連結串列,j 的取值是確定的。所以,取 k = 2。 正規一點兒的分析,如下: 因為 j 是 cl 的整數倍,並且是 cl 整數倍中滿足 j > s 最小的那個數。如果 s <= cl,那麼 j = cl;如果 s > cl,那麼 j < 2*s ,所以 j 的時間複雜度為 O(s + cl)。假設單向連結串列中結點的個數為 n,因為 s 和 cl 都是小於 n 的,所以 j = O(n) 。這是慢指標的時間複雜度,那麼快指標的就是 O(nk) 。所以取 k = 2 ,可以最小化演算法的執行時間。

如何確定環的起始結點?

在確定了 k 取2之後,我們考慮
  1. 當慢指標剛好進入環中,也就是慢指標走了 s 步之後,快指標走了 2*s 步,所以快指標在環中走了 2*s - s = s 步;
  2. 由於存在 s > cl 的情況,我們記快指標超出 Xs 的距離是 s % cl ;
  3. 此時,快指標需要追及慢指標的距離是 cl - s % cl;
  4. 因此,當慢指標在環中走了cl - s % cl 步後,快指標追上了慢指標;
所以,相遇之後的慢指標距離 Xs 的距離是 cl - (cl - s % cl) = s % cl 。因為有環的存在,我們可以把這個距離看成 s + M * cl,M 是正整數。所以相遇時的慢指標距離環的起始結點Xs 是 s 。這時,我們再設定另一個指標從單向連結串列的頭開始,以步長為 1 移動,移動 s 步後相遇,而這個相遇結點正好就是環的起始結點。

總結

至此,證明完畢,同時也回答了題目中的3個問題。雖然有點兒偏數學的證明推倒,而且我覺得面試的時候沒必要問這麼細,但是萬一面試官正好就問了這個問題,而你又能這樣詳細地說明解題思路,自然會給面試官留下一個非常好的印象,那麼你拿到offer的概率也就更大。

References