《程式設計之美》——程式設計判斷兩個連結串列是否相交
問題:
給出兩個連結串列的頭指標,判斷這兩個連結串列是否相交。假設兩個連結串列均不帶環。
分析與解法:
【解法一】
如果兩個連結串列都無環,則可以把第二個連結串列接在第一個連結串列後面,如果得到的連結串列有環,則說明這兩個連結串列相交。這裡如果有環,則第二個連結串列的表頭一定在環上,只需要從第二個連結串列開始遍歷,看是否會回到起點即可判斷。假設兩個連結串列長度分別為m和n,則時間複雜度為O(m+n)。
【解法二】
若兩個連結串列都無環且交於一點,那麼最後一個節點一定是共有的。可以先遍歷第一個連結串列,記錄最後一個節點,再遍歷第二個連結串列,將其最後一個節點與第一個連結串列的最後一個節點比較,若相同,則相交。時間複雜度也為O(m+n)。
擴充套件問題:
- 若連結串列可能有環該如何求解。
- 求兩個連結串列相交的第一個節點。
【問題1】
首先要判斷有頭指標的單鏈表是否有環。
解法:使用追逐的方法,設定兩個指標slow、fast,從頭指標開始,每次分別前進1步、2步。如果存在環,則兩者相遇;如果不存在環,fast遇到NULL退出。
bool isExistLoop(LinkList *head)
{
LinkList *slow = head;//slow指向頭指標
LinkList *fast = head;//fast指向頭指標
while(fast && fast -> next)//排除連結串列有單個節點或為空時的情況
{
slow = slow -> next;
fast = fast -> next -> next;
if(slow == fast)
break;
}
return !(fast == NULL || fast -> next == NULL});
}
- 若兩個都無環,回到原始問題;
- 若一個有環,一個無環,不用判斷了,肯定兩連結串列不相交;
- 若兩個都有環,判斷第一個連結串列的碰撞點(fast與slow相遇的節點)是否出現在第二個連結串列的環中,如果在,則相交。(相交時,環必定是兩連結串列共有的。)
【問題2】
同樣,使用追逐辦法先判斷是否存在環,分情況討論。
若無環,人為構造,將第一個連結串列的尾節點指向第二個連結串列,則構成一個帶環的單鏈表。這個問題就轉換成尋找帶環單鏈表的環入口節點。
尋找帶環單鏈表的環入口節點的方法:
碰撞點到交點的距離=頭指標到交點的距離,因此,分別從碰撞點、頭指標開始走,相遇的那個點就是交點。
LinkList findLoopPort(LinkList *head)
{
LinkList *slow = head;
LinkList *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;//slow指向頭指標
while(slow != fast)//此時fast指向碰撞點
{
slow = slow -> next;
fast = fast -> next;
}
return slow;
}
若有環,計算出兩連結串列的長度len1、len2(環的長度與環的頭指標到交點長度之和就是連結串列長度)。
如果len1>len2,則第一個連結串列的指標先走len1-len2,然後第二個連結串列的指標開始走,兩者相遇的點就是相交點;
如果len2>len1,則第二個連結串列的指標先走len2-len1,然後第一個連結串列的指標開始走,兩者相遇的點就是相交點。求環的長度的方法:
讓slow、fast從碰撞點開始走,再次碰撞所走過的運算元就是環的長度。
int loopLength(LinkList *head)
{
LinkList *slow = head, *fast = head;
LinkList *p;
int length = 0;
while(fast && fast -> next)
{
slow = slow -> next;
fast = fast -> next -> next;
if(slow == fast)
break;
}
if(fast == NULL || fast -> next == NULL})
return 0;
while(true)
{
slow = slow -> next;
fast = fast -> next -> next;
length++;
if(slow == fast)
break;
}
return length;
}