結合實戰和原始碼來聊聊Java中的SPI機制?
阿新 • • 發佈:2020-11-20
目錄
。這是因為,\(x+a=a+kb\) ,正好夠從連結串列頭部出發,走一段無環段(\(a\)),再把有環段走\(k\)圈(\(kb\))。
環形連結串列
如何判斷一個連結串列是否存在環
雜湊表法
雜湊表法的基本思路是:把訪問過的結點記錄下來,如果在遍歷中遇到了訪問過的結點,那麼可以確定連結串列中存在環。記錄訪問過的結點,最常用的方法就是使用雜湊表了。
有了這一點思路之後,我們很快可以寫出相應的題解程式碼:
public boolean hasCycle(ListNode head) { // 記錄已訪問過的結點 Set<ListNode> seen = new HashSet<>(); for (ListNode q = head; q != null; q = q.next) { if (seen.contains(q)) { // 遇到已訪問過的結點,確定連結串列存在環 return true; } seen.add(q); } // 遍歷迴圈正常退出,連結串列不存在環 return false; }
快慢指標法
我們讓快指標一次前進兩個結點,慢指標一次前進一個結點。當快慢指標都進入環路時,快指標會將慢指標「套圈」,從後面追上慢指標,如下圖所示。
這樣,如果快指標追上了慢指標,我們就可以判斷連結串列中存在環路。而如果連結串列中不存在環的話,快指標會永遠走在慢指標的前面,它們不會相遇,且快指標最終將到達連結串列末尾。
依照這個思路,我們可以寫出以下的題解程式碼。
public boolean hasCycle(ListNode head) { ListNode fast = head; ListNode slow = head; while (fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; // fast 和 slow 指向同一個結點,說明存在“套圈” if (fast == slow) { return true; } } // fast 到達連結串列尾部,則不存在環 return false; }
尋找環的入口結點
如果用雜湊表的方法,這道題其實和前一題是一樣的,沒什麼難度。
連結串列分為兩段:無環段和有環段。我們設無環段的長度為\(a\),環的長度為\(b\) 。當快慢指標相遇時,我們設慢指標已經走了\(x\)步,那麼快指標這時候已經走了$ 2x $步。快指標套圈了慢指標,也就是比慢指標多走了若干圈。我們可以列出公式:
\[2x-x=kb \]其中,\(k\)可以是任意正整數,因為快指標可能套了慢指標不止一圈。將上式化簡得到:
\[x=kb \]這個\(x\)恰好是慢指標走的步數。也就是說,慢指標目前前進的\(x\)步,正好是環的長度\(b\)的整數倍。
那麼,慢指標再走\(a\)步,就可以正好到達環的起點
如果我們在此時再用一個指標 q
從連結串列頭部出發。那麼指標 q
和慢指標 slow
會在走了\(a\)步以後恰好在環的起點處相遇。這樣,我們就可以知道環的起點了。
根據以上思路,我們可以寫出下面的題解程式碼:
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
// 快慢指標相遇,說明連結串列存在環
if (fast == slow) {
// 此時 slow 指標距離環的起點的距離恰好為 a
ListNode q = head;
while (q != slow) {
slow = slow.next;
q = q.next;
}
// slow 和 q 相遇的位置一定是環的起點
return slow;
}
}
// 連結串列不存在環,返回 null
return null;
}