環形鏈表入口節點
題目描述

思路分析:判斷是否有環
本題是判斷環形鏈表是否有環的進階題目。
環形鏈表題目利用了雙指針技巧,設置快慢兩個指針,每次快指針走兩步慢指針走一步。假如鏈表有環,那么快慢指針在環的部分終究會相遇。
判斷鏈表是否有環,比較簡單直接上代碼。
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
思路分析:如何找出入口節點?
方便分析作圖如下,假設a是鏈表頭節點,b是鏈表中環的入口節點,c是快慢指針相遇的節點。
三段路徑長度按順時針分別稱為ab,bc,cb。鏈表中環長度為s = bc + cb。

我們需要計算,在c點相遇的時候,快慢指針分別走了多遠?利用快慢指針長度的兩倍關系去求解問題。
快指針到c點走過的長度 s1 = ab + bc + n * s = ab + bc + n * (bc + cb)
快指針走的距離包括ab+bc的距離,再加上在圓環中環繞圈數的距離。
那么慢指針的長度如何計算?他在環中也走了多少圈?
這就需要理解一個概念:
從慢指針進入環時,到快慢指針相遇,慢指針走過的距離是一定小於等於環的大小。(如果整個鏈表是一個環,則慢指針走過距離等於環大小)
慢指針進入環后,可以看成快指針在后面追趕慢指針。
- 最好情況快指針就在b的左側,移動一步兩者便相遇。
- 最差情況為快指針在b的右側,需要多繞不到一圈的距離。
分析最差情況:假設慢指針走了一整圈環,回到b點時快指針才追上,慢指針移動長度為s,快指針移動長度則為2s-1。但實際上快指針速度是慢指針兩倍,即實際應走距離為2s。可以反證出在慢指針走完一圈之前,快指針必然追上慢指針。
理解了上述概念,可以很快得出,慢指針長度 s2 = ab + bc
已知了快指針是慢指針兩倍,即 2 *(ab + bc) = ab + bc + n * (bc + cb)
推出:ab = (n-1) * (bc + cb) + cb
含義為:鏈表頭到環入口的距離=相遇點到環入口的距離+(n-1)圈環長度
分析到這里,其實已經得出答案了。兩個鏈表相遇后,一個指針指向相遇的位置,一個指針指到鏈表的頭節點。兩個指針均向后移動,判斷當兩者相同時,該節點便是鏈表到環的入口節點。
代碼參考:
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
// 步驟一:使用快慢指針判斷鏈表是否有環
ListNode fast = head;
ListNode slow = head;
boolean hasCycle = false;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
hasCycle = true;
break;
}
}
// 步驟二:若有環,找到入環開始的節點
if (hasCycle) {
ListNode cur = head;
while (slow != cur) {
cur = cur.next;
slow = slow.next;
}
return slow;
}
return null;
}
公眾號:編程囈語,分享原創優質技術文章,歡迎大家關注。

