題目
Description
Given a linked list, return the node where the cycle begins.
If there is no cycle, return null.
Example
Given -21->10->4->5, tail connects to node index 1,return 10
Challenge
Follow up:
Can you solve it without using extra space?
分析
問題分解
basic problem
單鏈表中確定有無環
暴力方法是使用hash表統計節點的出現次數,O(n)的space。不過一般是希望以O(1)的space解決:
- 設置一個快指針fast和一個慢指針slow,它們同時從鏈表頭開始往后遍歷,快指針每次移動兩個位置,慢指針每次移動一個位置
- 如果在快指針訪問到null(即無環)之前,快指針和慢指針相遇,就說明有環
follow up
確定環入口入口的位置
和basic problem一樣,可以用hash表解決。而follow up的目的仍然是希望O(1)的space解決:
- 重復上述判定有環無環的過程
- 用一個新指針指向head,與slow指針同時一次移動一個位置
- 當head與slow相遇時,指針所指的節點即入口節點
證明
相遇一定有環
反證法:
如果無環,則fast一定比slow先到達null,不會相遇。因此,如果fast與slow相遇,則一定有環。
得證。
第一次相遇一定發生在slow第一次入環的過程中
該結論是下一步推導的前提。
設鏈表頭節點為head,環入口節點為entrance,head到entrance共有n個節點,環上共有m個節點。顯然,fast和slow相遇的節點一定在環中,設從entrance到這個節點共k個節點。
PS:代碼實現時,需要關注m、n、k等計算時是否包含head、entrance等,但證明時無需關心,僅僅是加減一個常數項的事情。
fast的速度是slow的兩倍。則,fast第一次到entrance時,slow到達鏈表中部節點mid,設head到mid共有s個節點,則此時,slow走了s個節點,fast走了2s個節點。我們讓slow再走s個節點,fast再走2s個節點,分情況討論:
如果entrance等于head
此情況下環最長。經過s個節點,slow恰好第一次到達entrance;經過2s個節點,fast恰好第二次到達entrance。在此過程中,slow有且僅有一次從mid走到entrance,fast有且僅有一次從head經過mid走到entrance,從而,fast與slow必然有且僅有一次在mid于entrance之間相遇,這是二者第一次相遇。由于相遇點一定在環中,因此第一次相遇一定發生在slow第一次入環的過程中。
如果entrance不等于head
此情況下,環均比第一種情況短。重復上述過程,slow仍然有且僅有一次從mid走到entrance,但fast卻由于環的縮短,可能不止一次與slow相遇并走到entrance。我們只關注第一次相遇,顯然,第一次相遇仍然發生在slow第一次入環的過程中。
得證。
如何尋找入口節點
我們現在知道,“第一次相遇一定發生在slow第一次入環的過程中”,那么此時slow共走了n+k
個節點,fast共移動了n+k + x*m
個節點。fast速度是slow的2倍,則有2*(n+k) = n+k + x*m
,其中x表示fast已經在環中走的圈數。由此可得,n+k = x*m
,從而n = (x-1)*m + m-k
(x>=1, m>=k)。
在有環鏈表中,我們只能基于移動和相遇進行判斷。basic problem中,我們希望通過相遇判斷是否有環,follow up中,可以試著用相遇找到entrance節點。
觀察式n = (x-1)*m + m-k
:
-
n
為head到entrance的節點數 -
m
為環的長度 -
m-k
為從fast、slow的相遇點到entrance的節點數
如果讓slow繼續走m-k
個節點,此時slow將恰好位于entrance;再循環y = x-1
圈,仍然處于entrance。增加一個新的指針p = head
,則可以這樣理解上式:使p和slow同時開始移動(slow從剛才的相遇點開始),都一次移動一個位置,則當p第一次經過n個節點走到entrance時,slow恰好先經過了m-k
個節點,再走了整y圈回到entrance。
此時,p與slow相遇,相遇點即為entrance。
代碼
/**
* Definition for ListNode.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int val) {
* this.val = val;
* this.next = null;
* }
* }
*/
public class Solution {
/**
* @param head: The first node of linked list.
* @return: The node where the cycle begins.
* if there is no cycle, return null
*/
public ListNode detectCycle(ListNode head) {
// write your code here
if (head == null) {
return null;
}
ListNode slow = head;
ListNode fast = head;
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;
}
ListNode p = head;
while (p != slow) {
p = p.next;
slow = slow.next;
}
return p;
}
}
本文鏈接:【刷題】Linked List Cycle II
作者:猴子007
出處:https://monkeysayhi.github.io
本文基于 知識共享署名-相同方式共享 4.0 國際許可協議發布,歡迎轉載,演繹或用于商業目的,但是必須保留本文的署名及鏈接。