前言
今天刷《劍指offer》的編程題,遇見一道挺有意思的題目,叫鏈表中環的入口節點,寫篇博客記錄一下。
描述
給出一個鏈表,在這個鏈表中至多存在一個環,要求:若鏈表中有環,則返回環的入口節點,若沒有環,返回null。
思路
我們可以設置兩個指針求解此問題:一個快指針fast,每次向前走兩個節點,一個慢指針low,每次向前走一個節點;我們首先需要知道一個結論:這兩個指針一快一慢向前走,若鏈表有環,則兩個指針一定會相遇;
這里先明確三個名稱:
頭節點:表示鏈表的第一個節點;環入口節點:若鏈表中有環,則進入這個環的第一個節點;相遇點:快慢指針相遇的節點;
我們設置三個變量來求解這個問題:
- 變量a:
頭節點到環入口節點的距離; - 變量b:從
環入口節點沿鏈表方向前進到相遇點的距離; - 變臉c:從
相遇點沿鏈表方向到環入口節點的距離;
所以,鏈表的長度 = a + b + c,而環的長度就是b+c;

畫個圖,把上面說的變量和名稱都標記上,我們不難看出,從開始到兩個指針相遇,慢指針low走過的距離為a+b,而快指針fast走過的距離為a + k×(b+c) + b,其中k是一個整數,且k>0(因為如果k==0,那快慢指針走過的距離就相等了);快指針fast每次前進兩個節點,而慢指針low每次前進一個節點,所以我們可以得到一個結論:相同時間內,快指針的走的路程是慢指針的兩倍;
根據以上結論,我們可以得到一個公式:2×(a+b) == a + k × (b+c) + b,而這個公式可以進行化簡,過程如下:
2×(a+b) == a + k × (b+c) + b;
2a + 2b == a + k × (b+c) + b;
a + b == k × (b+c);
a == k × (b+c) - b;
a == (k-1) × (b+c) + c; // 最終結果
經過一系列化簡,我們可以得到a == (k-1) × (b+c) + c,而上面我們說過,(b+c)就是鏈表中環的長度,所以這個公式可以解釋為:從鏈表頭節點到環入口節點的距離,等於環長度的k-1倍,再加上從相遇點到環入口節點的距離;
上面的解釋說明了什么?說明:兩個指針,一個從鏈表頭節點沿着鏈表向前走,另一個從快慢指針相遇點沿着鏈表向前走,最后它們會在環入口節點相遇(這里要好好理解一下);我們可以想象一下,指針在相遇點時,根據上面的最終結果,要向前走(k-1) × (b+c) + c這么長,而我們又知道,(b+c)就是環的長度,走一個b+c,就是饒了一圈,又回到了相遇點,所以當走完(k-1) × (b+c),其實又回到了相遇點;而這時,我們還要再向前走c個單位,c我們在上面已經說過了,就是相遇點到環入口的距離,所以走完c,指針就到了環入口了;而a我們也說過了,它是頭節點走到環入口的距離。所以,才有了上面的說明。
所以,這道題目的求解步驟就出來了:設置一個快指針fast,每次向前走兩個單位,設置一個慢指針low,每次向前走一個單位,當這兩個指針相遇,到達相遇點時,我們將設立一個指針指向鏈表頭節點,另一個指針指向相遇點,兩個指針速度均為1,沿着鏈表前進,最后它們相遇的位置,就是環入口節。
代碼
public ListNode EntryNodeOfLoop(ListNode pHead) {
if(pHead == null) {
return null;
}
// 設置快慢指針,快指針一次走兩步,慢指針一次走一步
ListNode fast = pHead;
ListNode low = pHead;
do {
// 快指針先走一步
fast = fast.next;
// 若fast為null,表示沒環,直接return空
if(fast == null) {
return null;
}
// 若不為null,再向前走一步
fast = fast.next;
// 慢指針向前走一步
low = low.next;
}while(fast != null && fast != low);
// low指針指向鏈表頭節點,fast指針不變,還是在相遇點
// 兩個指針速度均為1,向前走,再次相遇的點就是環入口節點
low = pHead;
while(low != fast) {
low = low.next;
fast = fast.next;
}
return low;
}
