1. 程式人生 > 實用技巧 >判斷連結串列是否有環,如果有,找到環的入口位置

判斷連結串列是否有環,如果有,找到環的入口位置

Linked List Cycle

原題連結:Linked List Cycle

判斷一個連結串列是否有環,空間複雜度是O(1)

如果不考慮空間複雜度,可以使用一個map記錄走過的節點,當遇到第一個在map中存在的節點時,就說明回到了出發點,即連結串列有環,同時也找到了環的入口。

不適用額外記憶體空間的技巧是使用快慢指標,即採用兩個指標walker和runner,walker每次移動一步而runner每次移動兩步。當walker和runner第一次相遇時,證明連結串列有環

以圖片為例,假設環的長度為R,當慢指標walker走到環入口時快指標runner的位置如圖,且二者之間的距離為S。在慢指標進入環後的t

時間內,快指標從距離環入口S處走了2t個節點,相當於從環入口走了S+2t個節點。而此時慢指標從環入口走了t個節點。

假設快慢指標一定可以相遇,那麼有S+2t−t=nR,即S+t=nR,如果對於任意的S,R,n,總可以找到一個t滿足上式,那麼就可以說明快慢指標一定可以相遇,滿足假設(顯然可以找到)

而實際上,由於S<R,所以在慢指標走過一圈之前就可以相遇

所以如果連結串列中有環,那麼當慢指標進入到環時,在未來的某一時刻,快慢指標一定可以相遇,通過這個也就可以判斷連結串列是否有環

程式碼如下

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        auto walker = head;
        auto runner = head;
        while(runner && runner->next)
        {
            walker = walker->next;
            runner = runner->next->next;
            if(walker == runner)
                return true;
        }
        return false;
    }
};

Linked List Cycle II

原題連結:Linked List Cycle II

如果連結串列有環,尋找環入口位置

以圖片為例,假設環入口距離連結串列頭的長度為L,快慢指標相遇的位置為cross,且該位置距離環入口的長度為S。考慮快慢指標移動的距離,慢指標走了L+S,快指標走了L+S+nR(這是假設相遇之前快指標已經繞環n圈)。由於快指標的速度是慢指標的兩倍,相同時間下快指標走過的路程就是慢指標的兩倍,所以有2(L+S)=L+S+nR,化簡得L+S=nR

n=1時,即快指標在相遇之前多走了一圈,即L+S=R,也就是L=R−S,觀察圖片,L表示從連結串列頭到環入口的距離,而R−

S表示從cross繼續移動到環入口的距離,既然二者是相等的,那麼如果採用兩個指標,一個從表頭出發,一個從cross出發,那麼它們將同時到達環入口。即二者相等時便是環入口節點

n>1時,上式為L=nR−S,LL仍然表示從連結串列頭到達環入口的距離,而nR−S可以看成從cross出發移動nR步後再倒退SS步,從cross移動nRnR步後回到cross位置,倒退S步後是環入口,所以也是同時到達環入口。即二者相等時便是環入口節點

所以尋找環入口的方法就是採用兩個指標,一個從表頭出發,一個從相遇點出發,一次都只移動一步,當二者相等時便是環入口的位置

程式碼如下

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        auto walker = head;
        auto runner = head;
        while(runner && runner->next)
        {
            walker = walker->next;
            runner = runner->next->next;
            if(walker == runner)
                break;
        }
        if(!runner || !runner->next)
            return nullptr;
        auto headWalker = head;
        auto crossWalker = walker;
        while(headWalker != crossWalker)
        {
            headWalker = headWalker->next;
            crossWalker = crossWalker->next;
        }
        return headWalker;
    }
};

其它的和環有關的題目記得還有

求環的長度

第一種方法是利用上面求出的環入口,再走一圈就可以求出長度,程式碼如下

int cycleLen(ListNode* head)
{
    auto cycleIn = detectCycle(head);
    int len = 1;
    auto walker = cycleIn;
    while(walker->next != cycleIn)
    {
        ++len;
        walker = walker->next;
    }
    return len;
}

第二種方法是當快慢指標相遇時,繼續移動直到第二次相遇,此時快指標移動的距離正好比慢指標多一圈,程式碼如下

int cycleLen(ListNode* head)
{
    auto walker = head;
    auto runner = head;
    while(runner && runner->next)
    {
        walker = walker->next;
        runner = runner->next;
        if(walker == runner)
            break;
    }
    int len = 0;
    while(runner && runner->next)
    {
        ++len;
        walker = walker->next;
        runner = runner->next;
        if(walker == runner)
            break;
    }
    return len;
}

from:https://blog.csdn.net/sinat_35261315/article/details/79205157