1. 題目描述
對於一個給定的鏈表,返回環的入口節點,如果沒有環,返回null
拓展:
你能給出不利用額外空間的解法麼?
2. 一般思路
當然這個題如果見都沒見過的話,可能就很難了,鏈表我們一般只給定鏈表頭,然後節點的兩個屬性,一個是next,一個是val,那麼如何根據這兩個屬性,就知道鏈表環的入口節點呢,好像一個指針是沒有辦法的。
- 判定有無環:快慢指針
然後我們好像聽說過快慢指針的方法,快慢指針可以用來求鏈表的中點、等分點,也可以用來求鏈表的倒數節點,但是如何求入口節點呢,這個好像還要麻煩一點。
想象一下,一個快指針一次兩步,一個慢指針一次一步,那麼如果有環的時候,這兩個指針怎麼樣?是不是肯定會遇上的?而且我們能確定的是,一定是在第一次快指針追上慢指針就遇上!爲什麼不能出現快指針直接越過慢指針的情況,這裏您可以去試一下,不會的。 - 求環的長度:固定指針
如何求出環的長度呢,既然快慢指針在有環的情況下能夠遇上,那麼當然是可以求出環的長度的。(一個指針動,另一個不動,跑一圈就行了)。 - 求入口節點:先後指針
有人說求出環的長度有啥用?可有用了,如果我們知道環的長度,那麼兩個指針從頭結點出發,一個指針先走環長度的步數,然後兩個指針一起走,遇上了之後不就是入口節點了嗎?
3. 神奇思路
這裏我更欣賞的是這樣的一種神奇的思路!先說明一下,X,Y分別代表表頭和入口節點,而Z代表着快慢指針的相遇位置!
不賣關子,先說結論:a=c。那試想,如果第一次相遇了,我們是不是隻需要把其中一個指針放到頭結點,然後兩個指針一起移動,下次相遇不就在入口節點了嗎??
證明也很簡單,我們假設快慢指針走了n步之後相遇在Z點,那麼快指針的路程是
慢指針的路程是
有意思的事情來了(怎麼這麼像朱一旦):(1)式和(2)式一等,不就得出了a=c的神仙結論了嗎?
接下來的事情就很簡單了。上代碼
4. 代碼
4.1 普通思路
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if (!head)
return head;
auto fast = head->next, slow = head;
while (fast != slow) {
if (!fast->next || !slow) {
return nullptr;
}
fast = fast->next->next;
slow = slow->next;
}
/*cout << fast->val << endl;
cout << slow->val << endl;*/
fast = fast->next;
int circle = 1;
while (fast != slow) {
fast = fast->next;
circle++;
/*cout << fast->val << endl;
cout << slow->val << endl;
cout << circle << endl;*/
}
int i = 0;
fast = head;
slow = head;
while (i<circle) {
fast = fast->next;
i++;
}
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
};
4.2 神仙思路
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if (!head)
return head;
auto fast = head, slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
break;
}
if(!fast||!fast->next)
return nullptr;
slow = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
};