1. 程式人生 > >給定一個有環連結串列,實現一個演算法返回環路的開頭結點

給定一個有環連結串列,實現一個演算法返回環路的開頭結點

檢測連結串列是否存在環路

有一種簡單的做法叫FastRunner/SlowRunner法。FastRunner一次移動兩步,而SlowRunner一次移動一步。這就好比兩輛賽車繞著同一條賽道以不同的速度前進,最終必然會碰到一起。

聰明的讀者可能會問:FastRunner會不會剛好“越過”SlowRunner,而不發生碰撞呢?絕無可能。假設FastRunner真的越過了SlowRunner,且SlowRunner處於位置i,FastRunner位於位置i+1。那麼,在前一步,SlowRunner就處於位置i-1,FastRunner處於位置((i+1)-2)或i-1。也就是說,兩者碰在一起了。

什麼時候碰在一起?

假定這個連結串列有一部分不存在環路,長度為k。

若運用上述演算法,FastRunner和SlowRunner什麼時候會碰在一起呢?

我們知道,SlowRunner每走p步,FastRunner就會走2p步。因此,當SlowRunner走了k步進入環路部分時,FastRunner已走了總共2k步,進入環路部分已有2k-k步或k步。由於k可能比環路長度大得多,實際上我們應該把它寫作mod(k, LOOP_SIZE)步,並用K表示。

對於之後的每一步,FastRunner和SlowRunner之間不是走遠一步就是更近一步,具體要看觀察的角度。也就是說,因為兩者處於圓圈中,當A以遠離B的方向走出q步時,同時也是向B更近了q步。綜上,我們得出以下幾點:

(1)SlowRunner處於環路中的0步位置;

(2)FastRunner處於環路中的K步位置;

(3)SlowRunner落後於FastRunner,相距K步;

(4)FastRunner落後於SlowRunner,相距LOOP_SIZE-K步;

(5)每過一個單位時間,FastRunner就會更接近於SlowRunner一步。

那麼,兩個節點什麼時候相遇?若FastRunner落後於SlowRunner,相距LOOP_SIZE-K步,並且每經過一個單位時間,FastRunner就走近SlowRunner一步,那麼,兩者將在LOOP_SIZE-K步之後相遇。此時,兩者與環路起始處相距K步,我們將這個位置稱為CollisionSpot。

如何找到環路起始處?

現在我們知道CollisionSpot與環路起始處相距K個節點。由於K=mod(k, LOOP_SIZE)(或者換句話說,k=K+M*LOOP_SIZE,其中M為任意整數),也可以說,CollisionSpot與環路起始處相距k個節點。例如,若有個環路長度為5個節點,有個節點N處於距離環路起始處2個節點的地方,我們也可以換個說法:這個節點處於距離環路起始處7個、12個甚至397個節點。

至此,CollisionSpot和LinkedListHead與環路起始處均相距k個節點。

現在,若用一個指標指向CollisionSpot,用另一個指標指向LinkedListHead,兩者與LoopStart均相距k個節點。以同樣的速度移動,這兩個指標會再次碰在一起——這次是在k步之後,此時兩個指標都指向LoopStart,然後只需返回該結點即可。

將全部整合在一起

總結一下,FastPointer的移動速度是SlowPointer的兩倍。當SlowPointer走了k個節點進入環路時,FastRunner已進入連結串列環路k個節點。也就是說FastRunner和SlowRunner相距LOOP_SIZE-k個節點。

接下來,若SlowPointer每走一個節點,FastRunner就走兩個節點,每走一次,兩者的距離就會更近一個節點。因此,在走了LOOP_SIZE-k次後,它們就會碰在一起。這時兩者距離環路起始處就有k個節點。

連結串列首部與環路起始處也相距k個節點。因此,若其中一個指標保持不變,另一個指標指向連結串列首部,則兩個指標就會在環路起始處相會。

根據之前的4部分描述,就能直接匯出下面的演算法。

(1)建立兩個指標:FastPointer和SlowPointer。

(2)SlowPointer每走一步,FastPointer就走兩步。

(3)兩者碰在一起時,將SlowPointer指向LinkedListHead,FastPointer保持不變。

(4)以相同速度移動SlowPointer和FastPointer,一次一步,然後返回新的碰撞處。

下面給出演算法程式碼:

LinkedListNode findBegining(LinkedListNode head) {
	LinkedListNode slow = head;
	LinkedListNode fast = head;

	/*找出碰撞處,將處於連結串列中LOOP_SIZE-k步的位置*/
	while(fast != null && fast.next != null) {
		slow = slow.next;
		fast = fast.next.next;
		if(slow == fast) { // 碰撞
			break;
		}
	}

	/*錯誤檢查,沒有碰撞處,也即沒有迴路*/
	if(fast == null || fast.next == null) {
		return null;
	}

	/*將slow指向首部,fast指向碰撞處,兩者距離環路
	 *起始處k步,若兩者以相同速度移動,則必定會在環
	 *路起始處碰在一起*/
	slow = head;
	while(slow != fast) {
		slow = slow.next;
		fast = fast.next;
	}

	/*至此兩者均指向環路起始處*/
	return fast;
}