目录
24. 两两交换链表中的节点 - 實作
思路
- 終止條件
兩兩鏈表交換有兩種狀況,分別是鏈表長度為奇數或偶數
根據這個狀況,條件設定要是能夠符合這兩種狀況
- cur→next ≠ NULL → 偶數狀況
- cur→next→next ≠ NULL → 奇數狀況
- 如何交換
-
cur 一定要在交換的兩節點前面
-
交換步驟
- cur→next 指向 cur→next→next.
- cur→next→next指向cur→next
- cur→next 指向 cur→next→next→next
-
交換步驟 → 圖示版
-
-
但如果直接座椅上交換步驟會出現一件事情
- 直接做步驟1會無法做步驟2,因為dummyhead指向B就會喪失A節點的位置
- 直接做步驟2會無法做步驟3,因為B指向A就會喪失C節點的位置
- 直接做步驟3會無法做步驟1,因為A指向C就會喪失B節點的位置
-
所以我們要先將cur→next (A)以及 cur→next→next→next(C)的位置先保存,至於cur→next→next(B)則會因為步驟1 直接成為cur→next
-
- 交換完後,return dummyhead→next.
Code
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyhead = new ListNode(0);
dummyhead->next = head;
ListNode* cur = dummyhead;
while(cur->next != NULL && cur->next->next != NULL){
ListNode* tmp1 = cur->next;
ListNode* tmp2 = cur->next->next->next;
cur->next = cur->next->next;
cur->next->next = tmp1;
tmp1->next = tmp2;
cur = tmp1;
}
return dummyhead->next;
}
};
19.删除链表的倒数第N个节点
思路
使用雙指針的思路,fast先走n+1次後,在開始讓slow走,直到fast走到NULL後,slow的下一個節點就是要刪除的節點。
為甚麼是n+1次,因為如果fast先走n次,slow會在fast指向NULL時,剛好走到要刪除的節點,但需要刪除的節點必須要在這個節點之前,所以如果fast只走n步,是不夠的,要走n+1次,讓slow剛好走到倒數第n個節點之前
Code
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode();
ListNode* fast = dummyHead;
ListNode* slow = dummyHead;
dummyHead->next = head;
n++; // 因為迴圈會n-- 所以需要先把要n+1的值補上
while(n-- && fast != NULL){
fast = fast->next;
}
while(fast != NULL){
fast = fast->next;
slow = slow->next;
}
ListNode* tmp = slow->next;
slow->next = slow->next->next;
return dummyHead->next;
}
};
160. 链表相交
思路
這題一開始想不明白,看卡哥的講解也有點矇,感謝算法訓練營的小夥伴,讓我理解好了
這題有兩個重點
-
數值相等,不等於位置相等,就像Example1 ,雖然A、B鏈表都有1,且都在8的前面,但是他們的位置不一致,所以真正的相交點是’8’
-
不會出現以下狀況,假設相交的,後面的數值一定會一樣
搞清楚這兩個重點,後面就比較簡單了
- 計算兩個鏈表的長度
- 固定A鏈表比較長,如果A鏈表比較短,讓A鏈表跟B鏈表交換
- 計算兩個鏈表的差值
- 將A鏈表的起始位置往後移兩個鏈表的差值,讓A的起始位置與B的開頭位置一致,使得兩個鏈表的末尾對齊(因為相交點後,後面一定都一樣)
- 如果開始遍歷後,都沒有一樣代表沒有相交,位置有一樣代表有相交return 當下的位置。
Code
錯誤代碼
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA = 0;
int lenB = 0;
ListNode* curA = headA;
ListNode* curB = headB;
while(curA != NULL){
curA = curA->next;
lenA++;
}
while(curB != NULL){
curB = curB->next;
lenB++;
}
curA = headA;
curB = headB;
if(lenB > lenA){
swap(curA, curB);
swap(lenA, lenB);
}
int gap = lenA - lenB;
while(gap--){
curA = curA->next;
}
while(curA != NULL){
curA = curA->next; // 應該先比較後移動,因為先移動了,所以出錯
curB = curB->next;
if(curA == curB){
return curA;
}
}
return NULL;
}
};
正確代碼
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA = 0;
int lenB = 0;
ListNode* curA = headA;
ListNode* curB = headB;
while(curA != NULL){
curA = curA->next;
lenA++;
}
while(curB != NULL){
curB = curB->next;
lenB++;
}
curA = headA;
curB = headB;
if(lenB > lenA){
swap(curA, curB);
swap(lenA, lenB);
}
int gap = lenA - lenB;
while(gap--){
curA = curA->next;
}
while(curA != NULL){
if(curA == curB){
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
142.环形链表II
思路
有三個重要思路
- fast和slow的相遇一定是在環中
- fast相對於slow每次都只會靠近slow一格,所以不論多遠,只要時間夠長,就一定會跟slow相遇
- 如果有環,slow與fast的相遇點以及head逐步往前兩者會相交,
Code
錯誤代碼
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* dummyHead = new ListNode();
ListNode* fast = dummyHead;
ListNode* slow = dummyHead;
dummyHead->next = head;
while(fast != NULL ){ //少判斷fast->next != NULL , 如果沒有判斷,可能就會導致fast->next->next是空指針操作
fast = fast->next->next;
slow = slow->next;
if(fast == slow){
ListNode* index1 = slow;
ListNode* index2 = dummyHead;
while(index1 != index2){
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return NULL;
}
};
正確代碼
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* dummyHead = new ListNode();
ListNode* fast = dummyHead;
ListNode* slow = dummyHead;
dummyHead->next = head;
while(fast != NULL && fast->next != NULL){
fast = fast->next->next;
slow = slow->next;
if(fast == slow){
ListNode* index1 = slow;
ListNode* index2 = dummyHead;
while(index1 != index2){
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return NULL;
}
};
總結
自己实现过程中遇到哪些困难
今天主要是在160.鏈表相交理解題目上出現了問題,我很慶幸我加入了算法訓練營,讓我在有問題的時候可以有人詢問
另外就是對於指標的邊界判斷以及順序,要避免務操作,這方面還需要再多做加強,現在還是很常在寫程式時沒有考慮到這些條件,而報錯
今日收获,记录一下自己的学习时长
今天大概學了3hr 主要是看卡哥的影片後,自己拿白紙重新捋一次想法,在寫代碼上其實就會輕鬆很多,思緒非常清晰,如果報錯,也可以重新對照想法與卡哥的解釋,看一下是不是自己沒考慮清楚
第二章 鏈表的學習總結
整理了四個重點如下:
- DummyHead非常重要,這個操作可以讓很多事情變得更加容易
- 雙指針非常重要,不論是206.反转链表還是142.环形链表II,都是解題的關鍵
- 在操作鏈表時,要注意會不會在操作中因為順序或者是沒有儲存到節點,導致操作出現問題,就像24. 两两交换链表中的节点 如果沒有先將cur→next (A)以及 cur→next→next→next(C)的位置先保存,會導致操作步驟出現問題
- 在寫題時,要注意非法index的產生,為了避免這個狀況,在****707.设计链表、****19.删除链表的倒数第N个节点都有做相應的操作
再來這裡必須要在回顧第三天的鏈表理論基礎中的鏈表定義以及操作
鏈表的定義
-
鏈表可以想像是一串粽子,粽子(資料),綁粽子的繩子(指標),這兩個形成一個節點,假設有連接兩個粽子之間的繩子則可以當作節點的連結。
鏈表程式定義
struct ListNode{ int val; //數據,這個粽子的內餡是甚麼 ListNode *next; //指標,指向下一個值,是否讓我可以連結下一個粽子的重要關鍵 ListNode(int x): val(x), next(NULL){} //節點的構造函數,就有點像是一開始要不要定義這個粽子是甚麼餡料 }
可不可以有多個數據,就是粽子的內餡可不可以多一點,可以!
但內餡一多,粽子就會變重,道理一樣,如果數據一多,節點就會佔更多空間
struct ListNode{ int val; //數據,這個粽子的內餡是甚麼 char val2; long val3; ListNode *next; //指標,指向下一個值,是否讓我可以連結下一個粽子的重要關鍵 ListNode(int x,char y, long z): val(x), val2(y), val3(z), next(NULL){} //節點的構造函數,就有點像是一開始要不要定義這個粽子是甚麼餡料 };
那可不可以不要有構造函數,可以,但在後續操作時,就不能直接賦值給節點了
//有構造函數的: ListNode* head = new ListNode(1,'2',3); //無構造函數的 ListNode* head = new ListNode(); head->val = 1; head->val2 = '2'; head->val3 = 3;
鏈表的操作
刪除節點
刪除C節點,只要將B節點的指標指向D,並釋放節點C即可
插入節點
插入E節點,將B節點的指向改向E,並將E節點指向C即可
鏈表一刷的學習總結如上。
參考資料
24. 两两交换链表中的节点
题目链接/文章讲解/视频讲解: https://programmercarl.com/0024.两两交换链表中的节点.html
19.删除链表的倒数第N个节点
题目链接/文章讲解/视频讲解:https://programmercarl.com/0019.删除链表的倒数第N个节点.html
160. 链表相交
题目链接/文章讲解:https://programmercarl.com/面试题02.07.链表相交.html
142.环形链表II
题目链接/文章讲解/视频讲解:https://programmercarl.com/0142.环形链表II.html