單鏈表

單鏈表

  • 單鏈表問題與思路
  1. 找出單鏈表的倒數(shù)第K個元素(僅允許遍歷一遍鏈表)
    使用指針追趕的方法。定義兩個指針fast和slow,fast先走K步,然后fast和slow同時繼續(xù)走。當(dāng)fast到鏈表尾部時,slow指向倒數(shù)第K個。注意要考慮鏈表長度應(yīng)該大于K

  2. 找出單鏈表的中間元素(僅允許遍歷一遍鏈表)
    使用指針追趕的方法。fast每次走一步,slow每次走兩步。當(dāng)fast到鏈表尾部時,slow指向鏈表的中間元素。

  1. 判斷單鏈表是否有環(huán)?
    方法一:使用指針追趕的方法。slow指針每次走一步,fast指針每次走兩步。如存在環(huán),則兩者相遇;如不存在環(huán),fast遇到NULL退出。
    方法二:使用p、q兩個指針,p總是向前走,但q每次都從頭開始走。

  2. 如何知道環(huán)的長度?
    記錄下碰撞點(或者找在環(huán)中任意一結(jié)點都可以),讓slow從碰撞點開始,繞著環(huán)走一圈,再次到碰撞點的位置時,所走過的結(jié)點數(shù)就是環(huán)的長度s。

  3. 如何找到環(huán)的入口?
    分別從碰撞點、頭指針開始走,相遇的那個點就是連接點。

  4. 判斷兩個鏈表(無環(huán))是否相交?
    方法一:采用暴力的方法,遍歷兩個鏈表,在遍歷的過程中進行比較,看節(jié)點是否相同。

    方法二:兩鏈表一旦相交,相交節(jié)點一定有相同的內(nèi)存地址,因此利用內(nèi)存地址建立哈希表,如此通過判斷兩個鏈表中是否存在內(nèi)存地址相同的節(jié)點判斷兩個鏈表是否相交。具體做法是:遍歷第一個鏈表,并利用地址建立哈希表,遍歷第二個鏈表,看看地址哈希值是否和第一個表中的節(jié)點地址值有相同即可判斷兩個鏈表是否相交。時間復(fù)雜度O((length(A)+ length(B))

    方法三:問題轉(zhuǎn)化法。先遍歷第一個鏈表到其尾部,然后將尾部的原本指向NULL的next指針指向第二個鏈表。這樣兩個鏈表就合成了一個鏈表,問題轉(zhuǎn)變?yōu)榕袛嘈碌逆湵硎欠裼协h(huán)?

    方法四:一旦兩個鏈表相交,那么兩個鏈表從相交節(jié)點開始到尾節(jié)點一定都是相同的節(jié)點。所以,如果他們相交的話,那么他們最后的一個節(jié)點一定是相同的,因此分別遍歷到兩個鏈表的尾部,然后判斷他們是否相同。

  5. 如何知道兩個單鏈表(可能有環(huán))是否相交
    思路:根據(jù)兩個鏈表是否有環(huán)來分別處理,若相交這個環(huán)屬于兩個鏈表共有
    (1)如果兩個鏈表都沒有環(huán)。
    (2)一個有環(huán),一個沒環(huán)。肯定不相交
    (3)兩個都有環(huán)。
    ①求出A的環(huán)入口,判斷其是否在B鏈表上,如果在,則相交。
    ② 在A鏈表上,使用指針追趕的方法,找到兩個指針碰撞點,之后判斷碰撞點是否在B鏈表上。如果在,則相交。

  6. 尋找兩個相交鏈表的第一個公共節(jié)點
    方法一:最簡單的方法就是先順序訪問其中一個鏈表,在每訪問一個節(jié)點時,都對另外一個鏈表進行遍歷,直到找到一個相等的節(jié)點位置,如果鏈表長度分別是m,n 則時間復(fù)雜度為O(mn)。
    方法二:(兩種情況)
    ① 相交的點,在環(huán)外。如果兩個鏈表有公共節(jié)點,那么該公共節(jié)點之后的所有節(jié)點都是兩個鏈表所共有的,所以長度一定也是相等的,如果兩個鏈表的總長度是相等的,那么我們對兩個鏈表進行遍歷,則一定同時到達(dá)第一個公共節(jié)點。但是鏈表的長度實際上不一定相同,所以計算出兩個鏈表的長度之差n,然后讓長的那個鏈表先移動n步,短的鏈表再開始向后遍歷,這樣他們一定同時到達(dá)第一個公共節(jié)點,我們只需要在向后移動的時候比較兩個鏈表的節(jié)點是否相等就可以獲得第一個公共節(jié)點。時間復(fù)雜度是O(m+n)。
    ② 相交的點在環(huán)內(nèi)。當(dāng)交點在環(huán)中時,此時的交點可以是A鏈表中的環(huán)入口點,也可以是B鏈表中環(huán)入口點。這是因為如果把B看出一個完整的鏈表,而A指向了B鏈表,則此時交點是A的環(huán)入口點。反之交點是鏈表B的環(huán)入口點。
    思路:根據(jù)上述分析,可以直接求出A的環(huán)入口點或者B的環(huán)入口點就可以了。



  • 單鏈表代碼實現(xiàn)
鏈表結(jié)點聲明如下:
struct ListNode{    
 int m_nKey;    
 ListNode * m_pNext;
};
  1. 求單鏈表中結(jié)點的個數(shù)
    這是最最基本的了,應(yīng)該能夠迅速寫出正確的代碼,注意檢查鏈表是否為空。時間復(fù)雜度為O(n)。參考代碼如下:
    1.  // 求單鏈表中結(jié)點的個數(shù)  
    2.  unsigned int GetListLength(ListNode * pHead)  
    3.  {  
    4.      if(pHead == NULL)  
    5.          return 0;  
    6.    
    7.      unsigned int nLength = 0;  
    8.      ListNode * pCurrent = pHead;  
    9.      while(pCurrent != NULL)  
    10.     {  
    11.         nLength++;  
    12.         pCurrent = pCurrent->m_pNext;  
    13.     }  
    14.     return nLength;  
    15. }  
  1. 找到單鏈表的中間節(jié)點:
SListNode* FindMidNode(SListNode* pHead)//pHead是鏈表的頭節(jié)點  
{  
    SListNode* fast = pHead;  
    SListNode* slow = pHead;  
    while (fast->next)  
    {  
        slow = slow->next;  
        fast = fast->next;  
        if (fast->next)  
        {  
            fast = fast->next;  
        }  
        else  
        {  
            break;  
        }  
    }  
    return slow;  
} 
  1. 判斷一個鏈表是否帶環(huán),若帶返回環(huán)的入口點(較難)
    上面已經(jīng)說過,通過快慢指針可以找到單鏈表中的任何節(jié)點,這也是通過驗證的。那么原理上也可以判斷鏈表是否帶環(huán):
    如果帶環(huán),快慢指針一定會在某個節(jié)點處相遇。(fast一直在環(huán)里轉(zhuǎn),總有一個時刻,slow追上fast,相遇)
    1.  SListNode* WhetherRing(SListNode* pHead)//判斷是否帶環(huán),若帶,返回相遇節(jié)點  
    2.  {  
    3.      if (pHead == NULL||pHead->next == NULL)  
    4.          return NULL;  
    5.      SListNode* fast = pHead;  
    6.      SListNode* slow = pHead;  
    7.      while (fast)  
    8.      {  
    9.          slow = slow->next;  
    10.         fast = fast->next;  
    11.         if (fast)  
    12.         {  
    13.             fast = fast->next;  
    14.         }  
    15.         else  
    16.         {  
    17.             return NULL;//沒有環(huán)  
    18.         }  
    19.         if (fast == slow)  
    20.         {  
    21.             return slow;//有環(huán)  
    22.         }  
    23.     }  
    24.     return NULL;  
    25. }  

如果鏈表帶環(huán)的話,我們就可以求出相遇的節(jié)點。然后從相遇節(jié)點處斷開帶環(huán)鏈表,一個指針從鏈表投節(jié)點處開始往后遍歷,一個指針從斷開處往后遍歷,相遇節(jié)點處就是環(huán)的入口點

    1.  SListNode* GetEnterNode(SListNode*  pHead)//找到鏈表環(huán)入口節(jié)點在哪(重點)  
    2.  {  
    3.      SListNode* start = pHead;  
    4.      SListNode* tmp = WhetherRing(pHead);  
    5.      while (start != tmp)  
    6.      {  
    7.          start = start->next;  
    8.          tmp = tmp->next;  
    9.      }  
    10.     return start;  
    11. }  
  1. 將單鏈表反轉(zhuǎn)
    從頭到尾遍歷原鏈表,每遍歷一個結(jié)點,將其摘下放在新鏈表的最前端。注意鏈表為空和只有一個結(jié)點的情況。時間復(fù)雜度為O(n)。參考代碼如下:
    1.  // 反轉(zhuǎn)單鏈表  
    2.  ListNode * ReverseList(ListNode * pHead)  
    3.  {  
    4.          // 如果鏈表為空或只有一個結(jié)點,無需反轉(zhuǎn),直接返回原鏈表頭指針  
    5.      if(pHead == NULL || pHead->m_pNext == NULL)    
    6.          return pHead;  
    7.    
    8.      ListNode * pReversedHead = NULL; // 反轉(zhuǎn)后的新鏈表頭指針,初始為NULL  
    9.      ListNode * pCurrent = pHead;  
    10.     while(pCurrent != NULL)  
    11.     {  
    12.         ListNode * pTemp = pCurrent;  
    13.         pCurrent = pCurrent->m_pNext;  
    14.         pTemp->m_pNext = pReversedHead; // 將當(dāng)前結(jié)點摘下,插入新鏈表的最前端  
    15.         pReversedHead = pTemp;  
    16.     }  
    17.     return pReversedHead;  
    18. }  
  1. 查找單鏈表中的倒數(shù)第K個結(jié)點(k > 0)
    最普遍的方法是,先統(tǒng)計單鏈表中結(jié)點的個數(shù),然后再找到第(n-k)個結(jié)點。注意鏈表為空,k為0,k為1,k大于鏈表中節(jié)點個數(shù)時的情況。時間復(fù)雜度為O(n)。代碼略。
    這里主要講一下另一個思路,這種思路在其他題目中也會有應(yīng)用。
    主要思路就是使用兩個指針,先讓前面的指針走到正向第k個結(jié)點,這樣前后兩個指針的距離差是k-1,之后前后兩個指針一起向前走,前面的指針走到最后一個結(jié)點時,后面指針?biāo)附Y(jié)點就是倒數(shù)第k個結(jié)點。
    參考代碼如下:

    1.  // 查找單鏈表中倒數(shù)第K個結(jié)點  
    2.  ListNode * RGetKthNode(ListNode * pHead, unsigned int k) // 函數(shù)名前面的R代表反向  
    3.  {  
    4.      if(k == 0 || pHead == NULL) // 這里k的計數(shù)是從1開始的,若k為0或鏈表為空返回NULL  
    5.          return NULL;  
    6.    
    7.      ListNode * pAhead = pHead;  
    8.      ListNode * pBehind = pHead;  
    9.      while(k > 1 && pAhead != NULL) // 前面的指針先走到正向第k個結(jié)點  
    10.     {  
    11.         pAhead = pAhead->m_pNext;  
    12.         k--;  
    13.     }  
    14.     if(k > 1 || pAhead == NULL)     // 結(jié)點個數(shù)小于k,返回NULL  
    15.         return NULL;  
    16.     while(pAhead->m_pNext != NULL)  // 前后兩個指針一起向前走,直到前面的指針指向最后一個結(jié)點  
    17.     {  
    18.         pBehind = pBehind->m_pNext;  
    19.         pAhead = pAhead->m_pNext;  
    20.     }  
    21.     return pBehind;  // 后面的指針?biāo)附Y(jié)點就是倒數(shù)第k個結(jié)點  
    22. } 
  1. 從尾到頭打印單鏈表
    對于這種顛倒順序的問題,我們應(yīng)該就會想到棧,后進先出。所以,這一題要么自己使用棧,要么讓系統(tǒng)使用棧,也就是遞歸。注意鏈表為空的情況。時間復(fù)雜度為O(n)。參考代碼如下:
    自己使用棧:

    1.  // 從尾到頭打印鏈表,使用棧  
    2.  void RPrintList(ListNode * pHead)  
    3.  {  
    4.      std::stack<ListNode *> s;  
    5.      ListNode * pNode = pHead;  
    6.      while(pNode != NULL)  
    7.      {  
    8.          s.push(pNode);  
    9.          pNode = pNode->m_pNext;  
    10.     }  
    11.     while(!s.empty())  
    12.     {  
    13.         pNode = s.top();  
    14.         printf("%d\t", pNode->m_nKey);  
    15.         s.pop();  
    16.     }  
    17. }  

使用遞歸函數(shù):


    1.  // 從尾到頭打印鏈表,使用遞歸  
    2.  void RPrintList(ListNode * pHead)  
    3.  {  
    4.      if(pHead == NULL)  
    5.      {  
    6.          return;  
    7.      }  
    8.      else  
    9.      {  
    10.         RPrintList(pHead->m_pNext);  
    11.         printf("%d\t", pHead->m_nKey);  
    12.     }  
    13. }  
  1. 已知兩個單鏈表pHead1 和pHead2 各自有序,把它們合并成一個鏈表依然有序
    這個類似歸并排序。尤其注意兩個鏈表都為空,和其中一個為空時的情況。只需要O(1)的空間。時間復(fù)雜度為O(max(len1, len2))。參考代碼如下:
    1.  ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2)  
    2.  {  
    3.      if(pHead1 == NULL)  
    4.          return pHead2;  
    5.      if(pHead2 == NULL)  
    6.          return pHead1;  
    7.      ListNode * pHeadMerged = NULL;  
    8.      if(pHead1->m_nKey < pHead2->m_nKey)  
    9.      {  
    10.         pHeadMerged = pHead1;  
    11.         pHeadMerged->m_pNext = MergeSortedList(pHead1->m_pNext, pHead2);  
    12.     }  
    13.     else  
    14.     {  
    15.         pHeadMerged = pHead2;  
    16.         pHeadMerged->m_pNext = MergeSortedList(pHead1, pHead2->m_pNext);  
    17.     }  
    18.     return pHeadMerged;  
    19. }  
  1. 判斷一個單鏈表中是否有環(huán)
    這里也是用到兩個指針。如果一個鏈表中有環(huán),也就是說用一個指針去遍歷,是永遠(yuǎn)走不到頭的。因此,我們可以用兩個指針去遍歷,一個指針一次走兩步,一個指針一次走一步,如果有環(huán),兩個指針肯定會在環(huán)中相遇。時間復(fù)雜度為O(n)。參考代碼如下:

    1.  bool HasCircle(ListNode * pHead)  
    2.  {  
    3.      ListNode * pFast = pHead; // 快指針每次前進兩步  
    4.      ListNode * pSlow = pHead; // 慢指針每次前進一步  
    5.      while(pFast != NULL && pFast->m_pNext != NULL)  
    6.      {  
    7.          pFast = pFast->m_pNext->m_pNext;  
    8.          pSlow = pSlow->m_pNext;  
    9.          if(pSlow == pFast) // 相遇,存在環(huán)  
    10.             return true;  
    11.     }  
    12.     return false;  
    13. }  
  1. 給出一單鏈表頭指針pHead和一節(jié)點指針pToBeDeleted,O(1)時間復(fù)雜度刪除節(jié)點pToBeDeleted
    對于刪除節(jié)點,我們普通的思路就是讓該節(jié)點的前一個節(jié)點指向該節(jié)點的下一個節(jié)點,這種情況需要遍歷找到該節(jié)點的前一個節(jié)點,時間復(fù)雜度為O(n)。對于鏈表,鏈表中的每個節(jié)點結(jié)構(gòu)都是一樣的,所以我們可以把該節(jié)點的下一個節(jié)點的數(shù)據(jù)復(fù)制到該節(jié)點,然后刪除下一個節(jié)點即可。要注意最后一個節(jié)點的情況,這個時候只能用常見的方法來操作,先找到前一個節(jié)點,但總體的平均時間復(fù)雜度還是O(1)。參考代碼如下:

    1.  void Delete(ListNode * pHead, ListNode * pToBeDeleted)  
    2.  {  
    3.      if(pToBeDeleted == NULL)  
    4.          return;  
    5.      if(pToBeDeleted->m_pNext != NULL)  
    6.      {  
    7.          pToBeDeleted->m_nKey = pToBeDeleted->m_pNext->m_nKey; // 將下一個節(jié)點的數(shù)據(jù)復(fù)制到本節(jié)點,然后刪除下一個節(jié)點  
    8.          ListNode * temp = pToBeDeleted->m_pNext;  
    9.          pToBeDeleted->m_pNext = pToBeDeleted->m_pNext->m_pNext;  
    10.         delete temp;  
    11.     }  
    12.     else // 要刪除的是最后一個節(jié)點  
    13.     {  
    14.         if(pHead == pToBeDeleted) // 鏈表中只有一個節(jié)點的情況  
    15.         {  
    16.             pHead = NULL;  
    17.             delete pToBeDeleted;  
    18.         }  
    19.         else  
    20.         {  
    21.             ListNode * pNode = pHead;  
    22.             while(pNode->m_pNext != pToBeDeleted) // 找到倒數(shù)第二個節(jié)點  
    23.                 pNode = pNode->m_pNext;  
    24.             pNode->m_pNext = NULL;  
    25.             delete pToBeDeleted;  
    26.         }     
    27.     }  
    28. }  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內(nèi)容