1. 程式人生 > >資料結構之連結串列基本操作總結

資料結構之連結串列基本操作總結

題意: 

如何找到環的第一個節點?

分析:
 1)先判斷是否存在環

使用兩個指標slow,fast。兩個指標都從表頭開始走,slow每次走一步,fast每次走兩步,如果fast遇到null,則說明沒有環,返回false;如果slow==fast,說明有環,並且此時fast超了slow一圈,返回true。

為什麼有環的情況下二者一定會相遇呢?因為fast先進入環,在slow進入之後,如果把slow看作在前面,fast在後面每次迴圈都向slow靠近1,所以一定會相遇,而不會出現fast直接跳過slow的情況。 

2)找環的第一個節點


設:連結串列頭是X,環的第一個節點是Y,slow和fast第一次的交點是Z。各段的長度分別是a,b,c,如圖所示。環的長度是L。slow和fast的速度分別是qs,qf。

第一次相遇時slow走過的距離:a+b,fast走過的距離:a+b+c+b。

因為fast的速度是slow的兩倍,所以fast走的距離是slow的兩倍,有 2(a+b) = a+b+c+b,可以得到a=c(這個結論很重要!)

發現L=b+c=a+b,也就是說,從一開始到二者第一次相遇,迴圈的次數就等於環的長度。

已經得到了結論a=c,那麼讓兩個指標分別從X和Z開始走,每次走一步,那麼正好會在Y相遇!也就是環的第一個節點。

struct ListNode {     int val;     ListNode *next;     ListNode
(int x):val(x),next(nullptr){} }; class Solution { public:     ListNode *detectCycle(ListNode *head)     {     if(head==nullptr)     return nullptr;     ListNode *slow=head;     ListNode *fast=head;     ListNode *slow2=head;     while(fast!=nullptr&&fast->next!=nullptr)     {
    slow=slow->next;     fast=fast->next->next;     if(fast==slow)     {     while(slow!=slow2)     {     slow=slow->next;     slow2=slow2->next;     }     return slow2;     }     }     return nullptr;     } };


【題目拓展】 
1. 環的長度是多少?
方法一

第一次相遇後,讓fast停著不走了,slow繼續走,記錄到下次相遇時迴圈了幾次。

方法二

第一次相遇時slow走過的距離:a+b,fast走過的距離:a+b+c+b。

因為fast的速度是slow的兩倍,所以fast走的距離是slow的兩倍,有 2(a+b) = a+b+c+b,可以得到a=c(這個結論很重要!)

我們發現L=b+c=a+b,也就是說,從一開始到二者第一次相遇,迴圈的次數就等於環的長度。

2. 如何找到環中第一個節點(即Linked List Cycle II)?
我們已經得到了結論a=c,那麼讓兩個指標分別從X和Z開始走,每次走一步,那麼正好會在Y相遇!也就是環的第一個節點。




      3. 如何將有環的連結串列變成單鏈表(解除環)?
         在上一個問題的最後,將c段中Y點之前的那個節點與Y的連結切斷即可。



      4. 如何判斷兩個單鏈表是否有交點?如何找到第一個相交的節點?
         先判斷兩個連結串列是否有環,如果一個有環一個沒環,肯定不相交;如果兩個都沒有環,判斷兩個列表的尾部是否相等;如果兩個都          有環,判斷一個連結串列上的Z點是否在另一個連結串列上。
      5、(接問題4)
如何找到第一個相交的節點?
        求出兩個連結串列的長度L1,L2(如果有環,則將Y點當做尾節點來算),假設L1<L2,用兩個指標分別從兩個連結串列的頭部開始走,長度         為L2的連結串列先走(L2-L1)步,然後兩個一起走,直到二者相遇。

單鏈表之合併兩個有序單鏈表  題目:已知兩個有序單鏈表head1、head2(假設連結串列元素為升序排列),把他們合併成一個連結串列,合併後依然有序;使用後遞迴和非遞迴方式實現;
 
//方法一:遞迴;
              設兩個連結串列的頭結點分別為head1和head2,如果head1為空,則直接返回head2,;如果head2為空
//              則直接返回head1;如果head1連結串列的第一個資料小於head2連結串列的第一個資料,則把head1連結串列的 //              元素儲存到新合併的連結串列中,遞迴遍歷去掉第一個元素的head1連結串列和整個head2連結串列。如果head1 //              連結串列的第一個元素>=head2連結串列的第一個元素,則把head2連結串列的第一個元素儲存到新合併的連結串列 //               中,遞迴遍歷整個head1連結串列和去除第一個元素後的head2連結串列。直到兩個連結串列的節點都被加入到 //               新合併的連結串列中。 //               遞迴終止條件:若head1為空,返回head2指標(head);若head2為空,返回head1指標(head); //        遞迴方法所使用的棧空間與連結串列的長度成正比。 // ListNode *mergeRecursive(ListNode *head1,ListNode *head2)//head1head2 {     if(head1==nullptr)     return head2;     if(head2==nullptr)     return head1;     ListNode *newHead=nullptr;     if(head1->val<head2->val)     {     newHead=head1;     newHead->next=mergeRecursive(head1->next,head2);     }     else     {     newHead=head2;         newHead->next=mergeRecursive(head1,head2->next);     }     return newHead; }
方法二:非遞迴;
                   分別用兩個指標head1和head2遍歷兩個連結串列,如果當前head1指向的資料小於head2指向的資料                    則將head1指向的節點歸併入合併後的連結串列中;否則將head2指向的節點歸併入合併後的連結串列中,                   如果有一個連結串列遍歷結束,則把未結束的連結串列連線到合併後的連結串列尾部; // ListNode *mergeList(ListNode *head1,ListNode *head2)//head1head2 {     ListNode *newHead=new ListNode(0);     ListNode *node=newHead;     while(head1!=nullptr&&head2!=nullptr)     {     if(head1->val<head2->val)     {     node->next=head1;     node=head1;     head1=head1->next;     }     else     {     node->next=head2;     node=head2;     head2=head2->next;     }     }     if(head1!=nullptr)     {     node->next=head1;     }     if(head2!=nullptr)     {     node->next=head2;     }     return newHead; // }