題目
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
單鏈表中確定有無環(huán)
暴力方法是使用hash表統(tǒng)計(jì)節(jié)點(diǎn)的出現(xiàn)次數(shù),O(n)的space。不過一般是希望以O(shè)(1)的space解決:
- 設(shè)置一個(gè)快指針fast和一個(gè)慢指針slow,它們同時(shí)從鏈表頭開始往后遍歷,快指針每次移動(dòng)兩個(gè)位置,慢指針每次移動(dòng)一個(gè)位置
- 如果在快指針訪問到null(即無環(huán))之前,快指針和慢指針相遇,就說明有環(huán)
follow up
確定環(huán)入口入口的位置
和basic problem一樣,可以用hash表解決。而follow up的目的仍然是希望O(1)的space解決:
- 重復(fù)上述判定有環(huán)無環(huán)的過程
- 用一個(gè)新指針指向head,與slow指針同時(shí)一次移動(dòng)一個(gè)位置
- 當(dāng)head與slow相遇時(shí),指針?biāo)傅墓?jié)點(diǎn)即入口節(jié)點(diǎn)
證明
相遇一定有環(huán)
反證法:
如果無環(huán),則fast一定比slow先到達(dá)null,不會(huì)相遇。因此,如果fast與slow相遇,則一定有環(huán)。
得證。
第一次相遇一定發(fā)生在slow第一次入環(huán)的過程中
該結(jié)論是下一步推導(dǎo)的前提。
設(shè)鏈表頭節(jié)點(diǎn)為head,環(huán)入口節(jié)點(diǎn)為entrance,head到entrance共有n個(gè)節(jié)點(diǎn),環(huán)上共有m個(gè)節(jié)點(diǎn)。顯然,fast和slow相遇的節(jié)點(diǎn)一定在環(huán)中,設(shè)從entrance到這個(gè)節(jié)點(diǎn)共k個(gè)節(jié)點(diǎn)。
PS:代碼實(shí)現(xiàn)時(shí),需要關(guān)注m、n、k等計(jì)算時(shí)是否包含head、entrance等,但證明時(shí)無需關(guān)心,僅僅是加減一個(gè)常數(shù)項(xiàng)的事情。
fast的速度是slow的兩倍。則,fast第一次到entrance時(shí),slow到達(dá)鏈表中部節(jié)點(diǎn)mid,設(shè)head到mid共有s個(gè)節(jié)點(diǎn),則此時(shí),slow走了s個(gè)節(jié)點(diǎn),fast走了2s個(gè)節(jié)點(diǎn)。我們讓slow再走s個(gè)節(jié)點(diǎn),fast再走2s個(gè)節(jié)點(diǎn),分情況討論:
如果entrance等于head
此情況下環(huán)最長(zhǎng)。經(jīng)過s個(gè)節(jié)點(diǎn),slow恰好第一次到達(dá)entrance;經(jīng)過2s個(gè)節(jié)點(diǎn),fast恰好第二次到達(dá)entrance。在此過程中,slow有且僅有一次從mid走到entrance,fast有且僅有一次從head經(jīng)過mid走到entrance,從而,fast與slow必然有且僅有一次在mid于entrance之間相遇,這是二者第一次相遇。由于相遇點(diǎn)一定在環(huán)中,因此第一次相遇一定發(fā)生在slow第一次入環(huán)的過程中。
如果entrance不等于head
此情況下,環(huán)均比第一種情況短。重復(fù)上述過程,slow仍然有且僅有一次從mid走到entrance,但fast卻由于環(huán)的縮短,可能不止一次與slow相遇并走到entrance。我們只關(guān)注第一次相遇,顯然,第一次相遇仍然發(fā)生在slow第一次入環(huán)的過程中。
得證。
如何尋找入口節(jié)點(diǎn)
我們現(xiàn)在知道,“第一次相遇一定發(fā)生在slow第一次入環(huán)的過程中”,那么此時(shí)slow共走了n+k
個(gè)節(jié)點(diǎn),fast共移動(dòng)了n+k + x*m
個(gè)節(jié)點(diǎn)。fast速度是slow的2倍,則有2*(n+k) = n+k + x*m
,其中x表示fast已經(jīng)在環(huán)中走的圈數(shù)。由此可得,n+k = x*m
,從而n = (x-1)*m + m-k
(x>=1, m>=k)。
在有環(huán)鏈表中,我們只能基于移動(dòng)和相遇進(jìn)行判斷。basic problem中,我們希望通過相遇判斷是否有環(huán),follow up中,可以試著用相遇找到entrance節(jié)點(diǎn)。
觀察式n = (x-1)*m + m-k
:
-
n
為head到entrance的節(jié)點(diǎn)數(shù) -
m
為環(huán)的長(zhǎng)度 -
m-k
為從fast、slow的相遇點(diǎn)到entrance的節(jié)點(diǎn)數(shù)
如果讓slow繼續(xù)走m-k
個(gè)節(jié)點(diǎn),此時(shí)slow將恰好位于entrance;再循環(huán)y = x-1
圈,仍然處于entrance。增加一個(gè)新的指針p = head
,則可以這樣理解上式:使p和slow同時(shí)開始移動(dòng)(slow從剛才的相遇點(diǎn)開始),都一次移動(dòng)一個(gè)位置,則當(dāng)p第一次經(jīng)過n個(gè)節(jié)點(diǎn)走到entrance時(shí),slow恰好先經(jīng)過了m-k
個(gè)節(jié)點(diǎn),再走了整y圈回到entrance。
此時(shí),p與slow相遇,相遇點(diǎn)即為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
本文基于 知識(shí)共享署名-相同方式共享 4.0 國(guó)際許可協(xié)議發(fā)布,歡迎轉(zhuǎn)載,演繹或用于商業(yè)目的,但是必須保留本文的署名及鏈接。