問題:
1.給定一個鏈表,判斷鏈表中是否有環(huán)。
2.給定一個鏈表,返回鏈表開始入環(huán)的第一個節(jié)點。 如果鏈表無環(huán),則返回 null。
說明:
不允許修改給定的鏈表。
時間復(fù)雜度:O(n)
空間復(fù)雜度:O(1)
解決辦法:
使用Floyd判圈算法,也叫龜兔賽跑算法。
原理:
設(shè)定每次兔子(快指針)跑兩步,烏龜(慢指針)跑一步(這樣設(shè)定是關(guān)鍵,也是該方法的巧妙之處),兩者同時起跑。如果鏈表中有環(huán),那么兔子會先進(jìn)入環(huán)中,等烏龜進(jìn)入環(huán)后,兩者都在環(huán)內(nèi)跑,所以兔子肯定會追上烏龜跟它相遇。
對于問題一,如果兔子能與烏龜相遇,就證明有環(huán),如果兔子能跑到盡頭停下來,證明沒有環(huán)。
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (true) {
//注意判斷fast是否走到盡頭有兩種情況,因為fast是一次跑兩步的,
//如果漏了判斷fast.next是否為空,最后一次執(zhí)行fast = fast.next.next時可能會報錯
if (fast == null || fast.next == null)
return false;//跑到盡頭,沒有環(huán)
slow = slow.next;//烏龜跑一步
fast = fast.next.next;//兔子跑兩步
if(slow == fast)return true;//龜兔相遇,有環(huán)
}
}
對于問題二,只要在起點再放一只一次跑一步的烏龜2號,當(dāng)環(huán)內(nèi)的龜兔相遇時起點的烏龜2號起跑,兩只烏龜相遇的位置就是環(huán)的第一個節(jié)點。原因:設(shè)烏龜跟兔子在環(huán)內(nèi)相遇時烏龜共跑了x步,那么兔子就跑了2x步,兔子比烏龜多跑x步,在烏龜進(jìn)入環(huán)之前,兔子已經(jīng)在環(huán)內(nèi)跑了n圈+c步(c>=0),在烏龜進(jìn)入環(huán)后,當(dāng)兔子比烏龜跑多了一圈的步數(shù)時就追上了烏龜或者在烏龜入環(huán)時剛好相遇,所以兔子比烏龜總共多跑m+n圈(m等0或1),所以m+n圈等于x步,即x是環(huán)的長度y的整數(shù)倍(x=ky)。再設(shè)環(huán)的第一個節(jié)點離起點a步(a>=0),相遇點離環(huán)的第一個節(jié)點b步(b>=0),a+b=x=ky。當(dāng)烏龜2號在起點和烏龜1號在龜兔的相遇點同時起跑時,烏龜2號走了a步,到達(dá)環(huán)的第一個節(jié)點,烏龜1號也走了a步,由于a+b是環(huán)長度的整數(shù)倍,所以烏龜1號也在環(huán)的第一個節(jié)點上,所以與烏龜2號相遇,即得到了環(huán)的第一個節(jié)點的位置。
public ListNode detectCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(true){
if(fast==null||fast.next==null)return null;//沒有環(huán)
fast=fast.next.next;
slow=slow.next;
if(fast==slow)break;//龜兔相遇
}
while(slow!=head){
slow=slow.next;
head=head.next;
}
return slow;
}
龜兔賽跑算法的巧妙之處在于兔子是烏龜速度的兩倍,才能得到烏龜步數(shù)是環(huán)長度的整數(shù)倍的關(guān)系,從而能快速找到環(huán)的第一個節(jié)點。另外,雙指針還有很多用法,不同的移動速度,不同的起點都會帶來奇妙的效果。