判斷連結串列是否有環,如果有,找到環的入口位置
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−
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