1. 程式人生 > >LeetCode:142. Linked List Cycle II

LeetCode:142. Linked List Cycle II

題目是這樣:

Given a linked list, return the node where the cycle begins. If there
is no cycle, return null.

Note: Do not modify the linked list.

Follow up: Can you solve it without using extra space?

如果連結串列有環,返回環開始的位置;否則,返回null。

這一題是跟著前面一題(判斷連結串列是否有環)的,這一題也需要判斷是否有環,所以也需要用到上一題的思路。上一題的思路大體上是使用兩個指標,一個一次走兩步,一個一次走一步。如果會相遇,則有環;否則,無環。
再來具體分析這一題。我們用兩個指標,pFast和pSlow,一個一次走兩步,一個一次走一步。先判斷有無環。無環,返回null。如果有環:
我們假設相遇的時候它們走了k步,環的長度為r。那麼有2k - k = nr(快的比慢的多走了n個環), k = nr。
假設連結串列的開始節點與環的開始節點之間的距離為s,環的開始節點與相遇的節點之間距離為m,也就是:


注意圖上的r-1。因為r代表的是整個環的長度,圖上所示沒有包含從尾節點到環開始節點的一小截,所以為r-1。
可以直觀得出:s = k - m。
所以s = nr - m。如果我們將它化為s = (n-1)r + (r-m)。這代表什麼?如果有個指標從相遇點出發,每次走一步,那麼走了r-m部之後,它就停在了環開始節點。再走(n-1)r步,還是停在環開始節點。在它出發的同時,有個節點從連結串列的開端也開始走,也是每次走一步,走了s步之後,它也是停在了環開始節點。所以這個等式表達的就是這兩個節點肯定會在環開始節點相遇。

以下是程式碼:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if (!head || !(head->next))
            return nullptr;
        auto pFast = head, pSlow = head;
        while (pFast && pFast->next) {
            pFast = pFast->next->next;
            pSlow = pSlow->next;
            if (pFast == pSlow)
                break;
        }
        if (pFast != pSlow)
            return NULL;
        auto pSlow2 = head;
        while (pSlow != pSlow2) {//attention
            pSlow = pSlow->next;
            pSlow2 = pSlow2->next;
        }
        return pSlow;       
    }
};

想象這樣一種情況:環的開始節點也是連結串列的開始節點。假設快指標和慢指標在除開始節點之外的某一節點相遇,那麼之後的pSlow和pSlow2豈不是會在程式碼中標註attention的地方陷入死迴圈嗎?注意,這種情況不會發生。如果環的開始節點也是連結串列的開始節點,那麼快指標和慢指標一定會在開始節點相遇,而不是其他位置。因為快指標會恰好比慢指標多走一圈,然後又在開始節點相遇。

這題有暴力解法:創造一個儲存節點的容器,遇到一個節點,就到容器中找有沒有,如果有,說明那就是環的開始,返回它。雖然方法不好,但是如果面試時遇到這題,第一時間把這種思路表述給面試官,也還是不錯的。雖然面試官肯定不會滿意就是了。。。
然後上面方法的難點我覺得可能在於需要一些巧勁吧,確實不太容易想到這種解法,需要自己畫出示意圖清晰地表述出來,慢慢分析才行。