資料結構之連結串列基本操作總結
題意:
如何找到環的第一個節點?
分析:
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相遇!也就是環的第一個節點。
【題目拓展】 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,;如果head2為空 // 則直接返回head1;如果head1連結串列的第一個資料小於head2連結串列的第一個資料,則把head1連結串列的 // 元素儲存到新合併的連結串列中,遞迴遍歷去掉第一個元素的head1連結串列和整個head2連結串列。如果head1 // 連結串列的第一個元素>=head2連結串列的第一個元素,則把head2連結串列的第一個元素儲存到新合併的連結串列 // 中,遞迴遍歷整個head1連結串列和去除第一個元素後的head2連結串列。直到兩個連結串列的節點都被加入到 // 新合併的連結串列中。 // 遞迴終止條件:若head1為空,返回head2指標(head);若head2為空,返回head1指標(head); // 遞迴方法所使用的棧空間與連結串列的長度成正比。 //方法一:遞歸 ListNode *mergeRecursive(ListNode *head1,ListNode *head2)//head1和head2均為鏈表的首節點,而非頭結點 { 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)//head1和head2均為鏈表的首節點,而非頭結點 { 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; //返回頭結點 }