題目描述:給一個鏈表,判斷其中環的起始結點,若沒有環則返回null。要求不改變鏈表,空間復雜度O(1)。
分析:這題與141題是同一個算法引出的一系列問題,即Floyd判圈算法,又稱龜兔賽跑算法,可以在有限狀態機、迭代函數或者鏈表上判斷是否存在環,并求出該環的起點與長度的算法。
算法的核心思想是設置快慢指針,例如設fast指針的速度是slow指針的2倍,若兩指針能相遇則說明有環,否則無環。
例如有環鏈表如下圖:
算法可以解決的問題有:
- 判斷鏈表中是否有環,即141題。
若就是一個循環鏈表,即Y點與X點是同一點,鏈表的尾結點鏈到頭結點,此時由于fast指針速度是slow指針的2倍,故起點就是兩者相遇點,每當slow指針回到起點則相遇,因為slow指針每走一圈即fast指針正好走兩圈。
若是像上圖的一般情況,則當slow指針到達Y處時fast指針已經繞圈走了一段距離,故不一定會在Y處相遇。列方程可得這是一個只與時間有關的方程,即過一段時間一定可以相遇。
若兩指針在fast走到末尾還未相遇,則說明無環。
- 計算環的長度。
第一種方法:第一次相遇后,讓slow繼續走,記錄到下次遇到fast時,即到達Z點時,slow走過的結點數。此時slow走了一圈,正好還在Z點相遇。
第二種方法:設環的長度為L,第一次相遇時slow走過的距離:a+b,fast走過的距離:a+b+c+b。因為fast的速度是slow的兩倍,所以fast走的距離是slow的兩倍,有 2(a+b) = a+b+c+b,得到a=c。發現L=b+c=a+b,也就是說,從一開始到二者第一次相遇,走過的結點數就等于環的長度。
找到環中第一個節點,即Y點。
根據結論a=c,讓兩個指針分別從X和Z開始走,每次走一步,則正好會在Y相遇,也就是環的第一個節點。將有環的鏈表變成單鏈表(解除環)
在上一個問題的最后,將c段中Y點之前的結點與Y的鏈接切斷即可。判斷兩個單鏈表是否有交點,并找到第一個相交的結點。
先判斷兩個鏈表是否有環,如果一個有環一個沒環,肯定不相交;如果兩個都沒有環,則判斷兩個列表的尾部是否相等;如果兩個都有環,判斷一個鏈表上的Z點是否在另一個鏈表上。
分別計算兩個鏈表的長度len1,len2,假設鏈表1較長,首先對較長的鏈表遍歷len1 - len2個結點到結點p,此時結點p與鏈表2頭結點到兩者相交的結點的距離相同,在此時同時遍歷兩個鏈表,直到相遇的結點為止,這個結點就是相交的結點。
在slow指針入環后,在最多走一圈的時間內必將遇到fast,時間復雜度O(n),空間O(1)。
代碼:
/**
* 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) {
ListNode *slow = head, *fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if ( slow == fast) //找到相遇點
{
ListNode *p = head;
while( p != slow) //slow從Z點出發,新指針P從起點X出發同時走,直到相遇
{
p = p->next;
slow = slow->next;
}
return p;
}
}
return nullptr;
}
};