圖解算法與數據結構
1、前言
今天開始的是雙指針!
下面一起來看看吧!!!
讓我們從一個經典問題開始:
給定一個鏈表,判斷鏈表中是否有環。
你可能已經使用 哈希表 提出瞭解決方案。但是,使用 雙指針 技巧有一個更有效的解決方案。
想象一下,有兩個速度不同的跑步者。如果他們在直路上行駛,快跑者將首先到達目的地。但是,如果它們在圓形跑道上跑步,那麼快跑者如果繼續跑步就會追上慢跑者。
這正是我們在鏈表中使用兩個速度不同的指針時會遇到的情況:
- 如果沒有環,快指針將停在鏈表的末尾。
- 如果有環,快指針最終將與慢指針相遇。
所以剩下的問題是:
這兩個指針的適當速度應該是多少?
一個安全的選擇是每次移動慢指針一步,而移動快指針兩步。每一次迭代,快速指針將額外移動一步。如果環的長度爲 M,經過 M 次迭代後,快指針肯定會多繞環一週,並趕上慢指針。
爲了讓你更懂,下面來看一個題吧!
2、實例
LeetCode 707,一個設計鏈表的題。
3、正文
一起來看一下:
-
雙指針初始化:
- 設雙指針
fast
,slow
指向鏈表頭部head
; fast
每輪走 1 步;slow
每輪走 1 步;
- 設雙指針
-
兩種情況:
- 第一種情況:不出意外,
fast
每輪再多走 1 步(這纔是名副其實的快指針~);
- 第二種情況:
fast
走到鏈表末端,下一節點爲空,說明鏈表無環,直接break
,返回NULL
(如果存在環,兩個指針必然會相遇,追擊問題,fast
速度是slow
的二倍~);
- 第一種情況:不出意外,
-
雙指針第一次相遇(當
fast == slow
時):- 設鏈表共有
a+b
個節點,其中 鏈表頭到環的入口 有a
個節點(不算環入口節點),環 有b
個節點,若兩指針分別走了f
,s
步(fast,slow
的英文縮寫); - 路程等於速度乘以時間,
fast
的速度是slow
的 2 倍,所以fast
走的步數是slow
的 2 倍,即f = 2*s
①; - 雙指針最後都是在環內繞圈直到重合,所以
fast
比slow
多走了n
個環的長度,即f = s + n*b
②; - 以上兩式 ① ② 相減得:
f = 2n*b
,s = n*b
,即fast
和slow
分別走了2n
,n
個 環。
- 設鏈表共有
-
雙指針第二次相遇(當
fast == slow
時):slow
指針不動,fast
重新指向鏈表頭節點(f = 0,s = n*b
);slow
和fast
同時每輪向前走 1 步;- 當
fast
走f = a
步時,slow
走s = a+nb
步,此時 兩指針重合,並同時指向環入口(b
是環的長度!整數倍b
就是環入口)。
-
返回
fast
指針指向的節點(slow
也行,因爲終止條件是fast == slow
)。
這個位置有個非常靈性的操作,即:
分析分析:
問:如何才能恰好在環入口節點相遇呢?
答:如果走過的路程滿足 a+n*b
,n
可以是任意自然數值(0,1,2…),先走 a
步到環入口節點,之後無論繞多少圈環(n*b
步)都會再次回到入口節點,即相遇了~~~~而現在這個時間點,slow
走過的步數是 nb
,只要想辦法再走 a
步停下來,就可以到環的入口,同時 fast
也要走 a
步,這樣兩者就相遇了!所以依然是雙指針法,fast
重新初始化爲鏈表頭 head
,這樣走 a
步就到了環入口,而 slow
也走 a
步,變成了 a+n*b
,到達了環入口節點。
妙啊!!!
4、代碼
/**
* 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 fast=head,slow=head;
while(fast){
fast=fast->next;
slow=slow->next;
if(fast) fast=fast->next;
else break;
if(fast==slow){
fast=head;
while(fast!=slow){
fast=fast->next;
slow=slow->next;
}
return fast;
}
}
return NULL;
}
};
如果有幸幫到你,請幫我點個【贊】,給個【關注】!如果能順帶【評論】給個鼓勵,我將不勝感激。
如果想要更多的資源,歡迎關注 @我是管小亮,文字強迫症MAX~