1. 程式人生 > 實用技巧 >結合實戰和原始碼來聊聊Java中的SPI機制?

結合實戰和原始碼來聊聊Java中的SPI機制?

目錄

環形連結串列

如何判斷一個連結串列是否存在環

雜湊表法

雜湊表法的基本思路是:把訪問過的結點記錄下來,如果在遍歷中遇到了訪問過的結點,那麼可以確定連結串列中存在環。記錄訪問過的結點,最常用的方法就是使用雜湊表了。

有了這一點思路之後,我們很快可以寫出相應的題解程式碼:

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\)步,就可以正好到達環的起點

。這是因為,\(x+a=a+kb\) ,正好夠從連結串列頭部出發,走一段無環段(\(a\)),再把有環段走\(k\)圈(\(kb\))。

如果我們在此時再用一個指標 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;
}

參考資料

nettee. 經典面試題:環形連結串列的判斷與定位. 面向大象程式設計