兩個鏈表找出第一個交點


題目:兩個單向鏈表,找出它們的第一個公共結點。

鏈表的結點定義為:

struct ListNode

{

      int       m_nKey;

      ListNode*   m_pNext;

};

分析:這是一道微軟的面試題。微軟非常喜歡與鏈表相關的題目,因此在微軟的面試題中,鏈表出現的概率相當高。

如果兩個單向鏈表有公共的結點,也就是說兩個鏈表從某一結點開始,它們的m_pNext都指向同一個結點。但由於是單向鏈表的結點,每個結點只有一個m_pNext,因此從第一個公共結點開始,之后它們所有結點都是重合的,不可能再出現分叉。所以,兩個有公共結點而部分重合的鏈表,拓撲形狀看起來像一個Y,而不可能像X。

看到這個題目,第一反應就是蠻力法:在第一鏈表上順序遍歷每個結點。每遍歷一個結點的時候,在第二個鏈表上順序遍歷每個結點。如果此時兩個鏈表上的結點是一樣的,說明此時兩個鏈表重合,於是找到了它們的公共結點。如果第一個鏈表的長度為m,第二個鏈表的長度為n,顯然,該方法的時間復雜度為O(mn)。

接 下來我們試着去尋找一個線性時間復雜度的算法。我們先把問題簡化:如何判斷兩個單向鏈表有沒有公共結點?前面已經提到,如果兩個鏈表有一個公共結點,那么 該公共結點之后的所有結點都是重合的。那么,它們的最后一個結點必然是重合的。因此,我們判斷兩個鏈表是不是有重合的部分,只要分別遍歷兩個鏈表到最后一 個結點。如果兩個尾結點是一樣的,說明它們用重合;否則兩個鏈表沒有公共的結點。

在上面的思路中,順序遍歷兩個鏈表到尾結點的時候,我們不能保證在兩個鏈表上同時到達尾結點。這是因為兩個鏈表不一定長度一樣。但如果假設一個鏈表比另一個長l個結點,我們先在長的鏈表上遍歷l個結點,之后再同步遍歷,這個時候我們就能保證同時到達最后一個結點了。由於兩個鏈表從第一個公共結點考試到鏈表的尾結點,這一部分是重合的。因此,它們肯定也是同時到達第一公共結點的。於是在遍歷中,第一個相同的結點就是第一個公共的結點。

在這個思路中,我們先要分別遍歷兩個鏈表得到它們的長度,並求出兩個長度之差。在長的鏈表上先遍歷若干次之后,再同步遍歷兩個鏈表,知道找到相同的結點,或者一直到鏈表結束。此時,如果第一個鏈表的長度為m,第二個鏈表的長度為n,該方法的時間復雜度為O(m+n)。

基於這個思路,我們不難寫出如下的代碼:

 

[cpp]  view plain  copy
 
  1. template<typename T>  
  2. struct ListNode  
  3. {  
  4.     T data;  
  5.     ListNode* pNext;  
  6. };  
  7. template<typename T> int GetListLength(ListNode<T>* pHead)  
  8. {  
  9.     int nLength = 0;  
  10.     while(pHead)  
  11.     {  
  12.         ++nLength;  
  13.         pHead = pHead->pNext;  
  14.     }  
  15.     return nLength;  
  16. }  
  17. template<typename T>  
  18. ListNode<T>* FindFirstCommonNode(ListNode<T>* pHead1, ListNode<T>* pHead2)  
  19. {  
  20.     int nLength1 = GetListLength(pHead1);  
  21.     int nLength2 = GetListLength(pHead2);  
  22.   
  23.     // 交換兩鏈表,是1為較長的鏈表  
  24.     if(nLength1 < nLength2)  
  25.     {  
  26.         ListNode<T>* pTemp = pHead1;  
  27.         pHead1 = pHead2;  
  28.         pHead2 = pTemp;  
  29.     }  
  30.     for(int i = 0; i < nLength1 - nLength2; ++i)  
  31.     {  
  32.         pHead1 = pHead1->pNext;  
  33.     }  
  34.     while(pHead1 && pHead1 != pHead2)  
  35.     {  
  36.         pHead1 = pHead1->pNext;  
  37.         pHead2 = pHead2->pNext;  
  38.     }  
  39.   
  40.     return pHead1;  
  41. }  

 

 

PS:兩個有公共結點而部分重合的鏈表,拓撲形狀看起來像一個Y,而不可能像X。即從公共節點開始之后的所有都相同!求出鏈表長度差,遍歷差數目長的鏈表,之后同步遍歷兩鏈表,第一個相同的節點即是結果。

 

PS:另外一種方法是把在一個鏈表尾部插入另一個鏈表,然后判斷合成的新鏈表是否有環。環入口即為第一個公共點。

 


上題是基於兩個鏈表無環考慮的。

1)判斷一個鏈表是否有環。

設置兩個指針(fast, slow),初始值都指向頭,slow每次前進一步,fast每次前進二步,如果鏈表存在環,則fast必定先進入環,而slow后進入環,兩個指針必定相遇。(當然,fast先行頭到尾部為NULL,則為無環鏈表)

 

[cpp]  view plain  copy
 
  1. // 判斷鏈表是否有環  
  2. template<typename T>  
  3. bool HasRing(ListNode<T>* pHead)  
  4. {  
  5.     ListNode<T>* pFast = pHead;  
  6.     ListNode<T>* pSlow = pHead;  
  7.     while(pFast && pFast->pNext)  
  8.     {  
  9.         pSlow = pSlow->pNext;  
  10.         pFast = pFast->pNext->pNext;  
  11.   
  12.         if(pSlow == pFast) break;  
  13.     }  
  14.   
  15.     return !pFast && !pFast->pNext;  
  16. }  

 

 

2)確定環入口:

當fast若與slow相遇時,slow肯定沒有走遍歷完鏈表,而fast已經在環內循環了n圈(1<=n)。

假設slow走了s步,則fast走了2s步(fast步數還等於s 加上在環上多轉的n圈),設環長為r,則:

2s = s + nr
s= nr

設入口環與相遇點距離為x,x<r,起點到環入口點的距離為a.

a = s - x = (n-1)r+ (r-x), 而r-x即為fast再次到環入口點時的步數

由此可知,從鏈表頭到環入口點等於(n-1)循環內環+相遇點到環入口點,於是我們從鏈表頭、與相遇點分別設一個指針,每次各走一步,兩個指針必定相遇,且相遇第一點為環入口點。

 

 

[cpp]  view plain  copy
 
  1. // 判斷鏈表環入口,如果沒有環,返回NULL  
  2. template<typename T>  
  3. ListNode* FindRingEntrance(ListNode<T>* pHead)  
  4. {  
  5.     ListNode<T>* pFast = pHead;  
  6.     ListNode<T>* pSlow = pHead;  
  7.   
  8.     // 先找到相遇點  
  9.     while(pFast && pFast->pNext)  
  10.     {  
  11.         pSlow = pSlow->pNext;  
  12.         pFast = pFast->pNext->pNext;  
  13.         if(pSlow == pFast) break;  
  14.     }  
  15.   
  16.     // 一個從起點,一個從相遇點重新遍歷,步長相同,當再次相遇時,就是環入口  
  17.     pSlow = pHead;  
  18.     while(pFast && pFast != pSlow)  
  19.     {  
  20.         pSlow = pSlow->pNext;  
  21.         pFast = pFast->pNext;  
  22.     }  
  23.   
  24.     return pFast;  
  25. }  

 

 

3)如過兩個鏈表可能有環,如何判斷兩個鏈表是否相交?以及找到兩個鏈表的第一個公共點?

 

如果兩個有環的鏈表相交,那么它們的環必定為公共環。如果交點不在環上,第一個公共點就是第一個交點。但是如果交點在環上,環的入口點不同。那么就以任一環的入口點為第一公共點。

 

有可能兩個鏈表均有環,但是它們並不相交。

 

算法:

找到兩個鏈表的環入口。如果有一個為NULL,一個非NULL,一定無交點,返回NULL。

如果均為NULL,變成兩個無環鏈表求交點。

如果均非NULL,則是判斷兩個有環的鏈表是否有交點:如果環入口相同,則它們實在分支上相交。否則判斷是否能從一個環中找到另一個的入口節點。

 

 

[cpp]  view plain  copy
 
  1. // 通用的鏈表求第一公共交點的函數  
  2. template<typename T>  
  3. ListNode<T>* FindFirstCommonNode_2(ListNode<T>* pHead1, ListNode<T>* pHead2)  
  4. {  
  5.     ListNode<T>* pEntrance1 = FindRingEntrance(pHead1);  
  6.     ListNode<T>* pEntrance2 = FindRingEntrance(pHead2);  
  7.   
  8.     ListNode<T>* pResult = NULL;  
  9.     if(pEntrance1 == pEntrance2)  
  10.     {  
  11.         ListNode<T>* pTemp1 = NULL, *pTemp2 = NULL;  
  12.         if(pEntrance1)  
  13.         {  
  14.             // 環入口點已經相交,尋找第一交點。  
  15.             pTemp1 = pEntrance1->pNext;  
  16.             pTemp2 = pEntrance2->pNext;  
  17.             pEntrance1->pNext = NULL;  
  18.             pEntrance2->pNext = NULL;  
  19.             pResult = FindFirstCommonNode(pHead1, pHead2);  
  20.             pEntrance1->pNext = pTemp1;  
  21.             pEntrance2->pNext = pTemp2;  
  22.         }  
  23.         else  
  24.         {  
  25.             // 無環單鏈表求第一交點  
  26.             pResult = FindFirstCommonNode(pHead1, pHead2);  
  27.         }  
  28.   
  29.         return pResult;  
  30.     }  
  31.     else if(pEntrance1 == NULL || pEntrance2 == NULL)  
  32.     {  
  33.         // 一有環,另一個無環,無交點  
  34.         return NULL;  
  35.     }  
  36.     else  
  37.     {  
  38.         // 入口點不同的兩環求交點  
  39.         pResult = pEntrance1->pNext;  
  40.         while(pResult != pEntrance2 && pResult != pEntrance1)  
  41.         {  
  42.             pResult = pResult->pNext;  
  43.         }  
  44.           
  45.         return pResult == pEntrance2 ? pResult : NULL;  
  46.     }  
  47. }  

 

參考鏈接:https://blog.csdn.net/wcyoot/article/details/6426436


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM