一.問題來源
昨晚看微博,發現於梁斌penny,他在說現在的面試制度考不出來真功夫,也就是基本功,面試題千篇一律的算法,看過會,不看就不會。期間提到了快慢指針求中位數。
查資料時我發現,這其實是計算機系統原理里的知識點。
二.快慢指針概念
快慢指針中的快慢指的是移動的步長,即每次向前移動速度的快慢。例如可以讓快指針每次沿鏈表向前移動2,慢指針每次向前移動1次。
三.快慢指針的應用
3.1 判斷單鏈表是否為循環鏈表
對於初學者來說,要解決這個問題,最可能采取的方法就是使用兩個循環。當外層循環步進一個節點時,內層循環就遍歷外層循環節點之后的所有節點,然后比較內外循環的兩個節點。若有節點地址相等,則表明該單鏈表有循環,反之則不存在循環。這種方法無疑效率比較低。
今天給大家介紹一個經典的方法,通過快慢指針來檢查單鏈表是否存在循環。其思路很簡單,大家可以想一下上體育課長跑的情景。當同學們繞着操場跑步的時候,速度快的同學會遙遙領先,最后甚至會超越其它同學一圈乃至n圈——這是繞圈跑。那么如果不是繞圈跑呢?速度快的同學則會一直領先直到終點,不會再次碰到后面的速度慢同學——不考慮地球是圓的這種情況。
快慢指針的設計思想也是這樣。快指針每次步進多個節點——這個視情況而定,慢指針每次只步進一個節點。那么如果該鏈表存在循環的話,快指針一定會再次碰到慢指針,反之則不存在循環。
讓快慢指針從鏈表頭開始遍歷,快指針向前移動兩個位置,慢指針向前移動一個位置;如果快指針到達NULL,說明鏈表以NULL為結尾,不是循環鏈表。如果 快指針追上慢指針,則表示出現了循環。
int isExitsLoop(LinkList L) {
LinkList fast, slow;
fast = slow = L;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
{
break;
}
}
return ((fast == NULL) || (fast->next == NULL));
}
注:不一定直接是一個環,可能說先共同走一段路,在尾部形成環。如果是第一種情況(長度為5,從1開始),看分解如下表。
1 | 3 | 5 | 2 | 4 | 1 |
1 | 2 | 3 | 4 | 5 | 1 |
3.2 在有序鏈表中尋找中位數
該方法在不借助計數器變量實現尋找中位數的功能。原理是:快指針的移動速度是慢指針移動速度的2倍,因此當快指針到達鏈表尾時,慢指針到達中點。程序還要考慮鏈表結點個數的奇偶數因素,當快指針移動x次后到達表尾(1+2x),說明鏈表有奇數個結點,直接返回慢指針指向的數據即可。如果快指針是倒數第二個結點,說明鏈表結點個數是偶數,這時可以根據“規則”返回上中位數或下中位數或(上中位數+下中位數)的一半。
while (fast&&slow)
{
if (fast->next==NULL)
return slow ->data;
else if (fast->next!= NULL && fast->next->next== NULL)
return (slow ->data + slow ->next->data)/2;
else
{
fast= fast->next;
fast= fast->next;
slow = slow ->next;
}
}
3.3 如果鏈表為存在環,如果找到環的入口點?
有一個單鏈表,其中可能有一個環,也就是某個節點的next指向的是鏈表中在它之前的節點,這樣在鏈表的尾部形成一環。
那么問題來了,如何判斷一個鏈表是不是這類鏈表?如果鏈表為存在環,如果找到環的入口點? 當fast若與slow相遇時,slow肯定沒有走遍歷完鏈表(不是一整個環,有開頭部分,如上圖)或者恰好遍歷一圈(未做驗證,看我的表格例子,在1處相遇)。於是我們從鏈表頭、與相遇點分別設一個指針,每次各走一步,兩個指針必定相遇,且相遇第一點為環入口點(慢指針走了n步,第一次相遇在c點,對慢指針來說n=s+p,也就是說如果慢指針從c點再走n步,又會到c點,那么順時針的CB距離是n-p=s,但是我們不知道s是幾,那么當快指針此時在A點一步一步走,當快慢指針相遇時,相遇點恰好是圓環七點B(AB=CB=s))。
node* findLoopPort(node *head) {
node *fast, *slow;
fast = slow = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
break;
}
}
if ((fast == NULL) || (fast->next == NULL)) {
return NULL;
}
slow = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
3.4 擴展問題
判斷兩個單鏈表是否相交,如果相交,給出相交的第一個點(兩個鏈表都不存在環)。
比較好的方法有兩個:
1.將其中一個鏈表首尾相連,檢測另外一個鏈表是否存在環,如果存在,則兩個鏈表相交,而檢測出來的依賴環入口即為相交的第一個點。
2.如果兩個鏈表相交,那個兩個鏈表從相交點到鏈表結束都是相同的節點,我們可以先遍歷一個鏈表,直到尾部,再遍歷另外一個鏈表,如果也可以走到同樣的結尾點,則兩個鏈表相交。
這時我們記下兩個鏈表length,再遍歷一次,長鏈表節點先出發前進(lengthMax-lengthMin)步,之后兩個鏈表同時前進,每次一步,相遇的第一點即為兩個鏈表相交的第一個點。
四.參考文獻及結束語
4.1 問題1
相差多少?才能保證相遇?如果從物理學的角度理解(s-t曲線),肯定相遇,那么問題是如何盡快相遇(也就是快慢指針相差幾倍才能使A點盡可能靠近源點,也就是說相遇盡可能早,這樣復雜度就低了)?看下圖,自己研究吧,筆者未做詳細探索。
4.2 問題2
s=t,s=2t和s=3t,s=6t的效果一樣嗎?
4.3 感想
指針可以相差倍數,那也可以相差固定位數啦?比如求鏈表的倒數第n位.
4.4 參考文獻
http://anyhu.blog.sohu.com/184515249.html
http://www.nowamagic.net/librarys/veda/detail/1842