收集了一下鏈表常見的面試題:
1、如何判斷一個單鏈表有環
2、如何判斷一個環的進入點在哪裡
3、如何知道環的長度
4、如何知道兩個單鏈表(無環)是否相交
5、如果兩個單鏈表(無環)相交,如何知道它們相交的第一個節點是什麼
6、如何知道兩個單鏈表(有環)是否相交
7、如果兩個單鏈表(有環)相交,如何知道它們相交的第一個節點是什麼
1、採用快慢步長法。令兩個指標p和q分別指向頭結點,p每次前進一步,q每次前進兩步,如果p和q能重合,則有環。可以這麼理解,這種做法相當於p靜止不動,q每次前進一步,所有肯定有追上p的時候。
我們注意到,指標p和q分別以速度為1和2前進。如果以其它速度前進是否可以呢?
假設p和q分別以速度為v1和v2前進。如果有環,設指標p和q第一次進入環時,他們相對於環中第一個節點的位移地址分別為a和b(可以把位移地址理解為節點個數)
這樣,可以看出,鏈表有環的充要條件就是某一次迴圈時,指標p和q的值相等,就是它們相對環中首節點的位移量相等。我們設環中的結點個數為n,程式迴圈了m次。
由此可以有下面等式成立:(mod(n)即對n取餘)
(a+m*v1)mod(n) = (b+m*v2) mod(n)
設等式左邊mod(n)的最大整數為k1,等式右邊mod(n)的最大整數為k2,則
(a+m*v1)-k1*n = (b+m*v2)-k2*n
整理以上等式:
m= |((k2-k1)*n+a-b)/( v2-v1)|
①
如果是等式①成立,就要使迴圈次數m為一整數。顯然如果v2-v1為1,則等式成立。
這樣p和q分別以速度為v1和v2且|v2-v1|為1時,按以上演算法就可找出鏈表中是否有環。當然|v2-v1|不為1時,也可能可以得出合格m。
[cpp]
view plaincopyprint?
- bool IsExitsLoop(slist *head)
- {
- slist *slow = head, *fast = head;
-
- while ( fast && fast->next )
- {
- slow = slow->next;
- fast = fast->next->next;
- if ( slow == fast )
break;
- }
-
- return !(fast == NULL || fast->next == NULL);
- }
bool IsExitsLoop(slist *head){ slist *slow = head, *fast = head; while ( fast && fast->next ) { slow = slow->next; fast = fast->next->next; if ( slow == fast ) break; } return !(fast == NULL || fast->next == NULL);}
時間複雜度分析:假設甩尾(在環外)長度為 len1(結點個數),環內長度為 len2,鏈表總長度為n,則n=len1+len2 。當p步長為1,q步長為2時,p指標到達環入口需要len1時間,p到達入口後,q處於哪裡不確定,但是肯定在環內,此時p和q開始追趕,q最長需要len2時間就能追上p(p和q都指向環入口),最短需要1步就能追上p(p指向環入口,q指向環入口的前一個節點)。事實上,每經過一步,q和p的距離就拉近一步,因此,經過q和p的距離步就可以追上p。因此總時間複雜度為O(n),n為鏈表的總長度。
2、分別從鏈表頭和碰撞點,同步地一步一步前進掃描,直到碰撞,此碰撞點即是環的入口。
證明如下:
鏈表形狀類似數字 6 。
假設甩尾(在環外)長度為 a(結點個數),環內長度為 b 。
則總長度(也是總結點數)為 a+b 。
從頭開始,0 base 編號。
將第 i 步訪問的結點用 S(i) 表示。i = 0, 1 ...
當 i<a 時,S(i)=i ;
當 i≥a 時,S(i)=a+(i-a)%b 。
分析追趕過程。
兩個指標分別前進,假定經過 x 步後,碰撞。則有:S(x)=S(2x)
由環的周期性有:2x=tb+x 。得到 x=tb 。
另,碰撞時,必須在環內,不可能在甩尾段,有 x>=a 。
連接點為從起點走 a 步,即 S(a)。
S(a) = S(tb+a) = S(x+a)。
得到結論:從碰撞點 x 前進 a 步即為連接點。
根據假設易知 S(a-1) 在甩尾段,S(a) 在環上,而 S(x+a) 必然在環上。所以可以發生碰撞。
而,同為前進 a 步,同為連接點,所以必然發生碰撞。
綜上,從 x 點和從起點同步前進,第一個碰撞點就是連接點。
[cpp]
view plaincopyprint?
- slist* FindLoopPort(slist *head)
- {
- slist *slow = head, *fast = 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;
- }
slist* FindLoopPort(slist *head){ slist *slow = head, *fast = 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;}
時間複雜度分析:假設甩尾(在環外)長度為 len1(結點個數),環內長度為 len2 。則時間複雜度為“環是否存在的時間複雜度”+O(len1)
3、從碰撞點開始,兩個指標p和q,q以一步步長前進,q以兩步步長前進,到下次碰撞所經過的操作次數即是環的長度。這很好理解,比如兩個運動員A和B從起點開始跑步,A的速度是B的兩倍,當A跑玩一圈的時候,B剛好跑完兩圈,A和B又同時在起點上。此時A跑的長度即相當於環的長度。
假設甩尾(在環外)長度為 len1(結點個數),環內長度為 len2 ,則時間複雜度為“環是否存在的時間複雜度”+O(len2)。
4、法一:將鏈表A的尾節點的next指標指向鏈表B的頭結點,從而構造了一個新鏈表。問題轉化為求這個新鏈表是否有環的問題。
時間複雜度為環是否存在的時間複雜度,即O(length(A)+length(B)),使用了兩個額外指標
法二:兩個鏈表相交,則從相交的節點起,其後的所有的節點都是都是兩個鏈表共有的。因此,如果它們相交,則最後一個節點一定是共有的。因此,判斷兩鏈表相交的方法是:遍曆第一個鏈表,記住最後一個節點。然後遍曆第二個鏈表,到最後一個節點時和第一個鏈表的最後一個節點做比較,如果相同,則相交。
時間複雜度:O(length(A)+length(B)),但是只用了一個額外指標儲存最後一個節點
5、將鏈表A的尾節點的next指標指向鏈表B的頭結點,從而構造了一個環。問題轉化為求這個環的入口問題。
時間複雜度:求環入口的時間複雜度
6、分別判斷兩個鏈表A、B是否有環(注,兩個有環鏈表相交是指這個環屬於兩個鏈表共有)
如果僅有一個有環,則A、B不可能相交
如果兩個都有環,則求出A的環入口,判斷其是否在B鏈表上,如果在,則說明A、B相交。
時間複雜度:“環入口問題的時間複雜度”+O(length(B))
7、分別計算出兩個鏈表A、B的長度LA和LB(環的長度和環到進入點長度之和就是鏈表長度),參照問題3。
如果LA>LB,則鏈表A指標先走LA-LB,鏈表B指標再開始走,則兩個指標相遇的位置就是相交的第一個節點。
如果LB>LA,則鏈表B指標先走LB-LA,鏈表A指標再開始走,則兩個指標相遇的位置就是相交的第一個節點。
時間複雜度:O(max(LA,LB))
轉自:http://blog.csdn.net/vividonly/article/details/6673758