生活随笔
收集整理的這篇文章主要介紹了
链表经典题
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
題目
序號題目難度鏈接外鏈方法
| 1. | 刪除所有結點 | 簡單 | 跳轉 | LeetCode | 雙指針 |
| 2. | 反轉鏈表 | 簡單 | 跳轉 | LeetCode | 雙指針or取頭構造 |
| 3. | 取鏈表中間結點 | 簡單 | 跳轉 | LeetCode | 快慢指針 |
| 4. | 取鏈表倒數結點 | 簡單 | 跳轉 | 牛客 | 雙指針 |
| 5. | 合并鏈表 | 簡單 | 跳轉 | LeetCode | 插入 |
| 6. | 分割鏈表 | 簡單 | 跳轉 | 牛客 | 插入 |
| 7. | 鏈表回文結構 | 簡單 | 跳轉 | 牛客 | 雙指針+逆置 |
| 8. | 相交鏈表 | 簡單 | 跳轉 | LeetCode | 消除差距一起走 |
| 9. | 環狀鏈表 | 簡單 | 跳轉 | LeetCode | 雙指針+追蹤 |
| 10. | 環狀鏈表2 | 中等 | 跳轉 | LeetCode | 斷環+相交鏈表 |
| 11. | 復雜鏈表的復制 | 中等 | 跳轉 | LeetCode | 錯位復制 |
| 12. | 鏈表的插入排序 | 中等 | 跳轉 | LeetCode | 哨兵結點+插入 |
| 13. | 鏈表移除重復元素 | 中等 | 跳轉 | 牛客 | 三指針 |
一:刪除結點中給定值的所有結點
法一:構建頭結點法
此題所給出的是一個沒有頭結點的鏈表,沒有頭結點的鏈表在操作時對于第一個結點的處理非常苦難,要考慮很多種情況,所以我們可以自己造一個頭結點,最終返回的時候再head放到該放到的位置上。這種方法其實有點作弊的嫌疑,但是無疑是一種非常好的方法
struct ListNode
* removeElements(struct ListNode
* head
, int val
)
{struct ListNode
* dummy
=(struct ListNode
*)malloc(sizeof(struct ListNode
));dummy
->next
=head
;dummy
->val
=NULL;struct ListNode
* pre
=dummy
;struct ListNode
* cur
=head
;while(cur
!=NULL){if(cur
->val
!=val
){cur
=cur
->next
;pre
=pre
->next
;}else{pre
->next
=cur
->next
;cur
=cur
->next
;}}head
=dummy
->next
;free(dummy
);return head
;
} 法二:迭代法
這種方法要注意一點,由于pre指針在初始狀態下是空,cur指針首先指向head,所以如果第一個結點就是要刪除的結點,那么進行刪除操作時“pre->next=cur->next”的操作顯然就是有問題的,所以說對于這種情況要特殊處理
struct ListNode
* removeElements(struct ListNode
* head
, int val
)
{struct ListNode
* pre
=NULL;struct ListNode
* cur
=head
;while(cur
!=NULL){if(head
->val
==val
){head
=cur
->next
;free(cur
);cur
=head
;}else{if(cur
->val
==val
){pre
->next
=cur
->next
;free(cur
);cur
=pre
;}else{pre
=cur
;cur
=cur
->next
;}}}return head
;
}
二:反轉單鏈表
法一:迭代法
使用迭代的方法逆置鏈表,需要用到三個指針
struct ListNode
* reverseList(struct ListNode
* head
)
{struct ListNode
* pre
=head
;struct ListNode
* cur
=NULL;struct ListNode
* save
;while(pre
!=NULL){save
=pre
;pre
=pre
->next
;save
->next
=cur
;cur
=save
;}return cur
;
}
法二:頭插法
把原來的結點取下來,然后進行頭插
struct ListNode
* newHead
=NULL;struct ListNode
* cur
=head
;while(cur
!= NULL){head
=cur
;cur
=cur
->next
;head
->next
=newHead
;newHead
=head
;}return newHead
;
三:取鏈表的中間結點
法一:普通解法
這種方法是最容易想到的方法,首先遍歷鏈表,得到總的結點數,然后找出中間節點,再次遍歷即可。
int count
=0;struct ListNode
* cur
=head
;while(cur
!=NULL){count
++;cur
=cur
->next
;}int ret
=count
/2;cur
=head
;while(ret
){cur
=cur
->next
;ret
--;}return cur
;
法二:雙指針法
一般來說,遍歷一次或者稍加巧妙的方法,就可以用到雙指針。定義一個慢指針和快指針,快指針每次走兩步,慢指針每次走一步,最后慢指針所指就是中間節點
struct ListNode
* slow
=head
;struct ListNode
* fast
=head
;while(fast
->next
!=NULL){fast
=fast
->next
->next
;slow
=slow
->next
;if(fast
==NULL)break;}return slow
;
四:取鏈表倒數第K個結點
此題可以用樸素解法,也可以將鏈表導致,求其第k個結點。但更為好的方法還是雙指針法
struct ListNode
* FindKthToTail(struct ListNode
* pListHead
, int k
)
{struct ListNode
* slow
=pListHead
;struct ListNode
* fast
=pListHead
;while(k
--){if(fast
)fast
=fast
->next
;elsereturn NULL;}while(fast
!=NULL){slow
=slow
->next
;fast
=fast
->next
;}return slow
;
}
五:合并鏈表
思路不難,創建新的鏈表,然后分別遍歷兩個原鏈表,把小的插入
struct ListNode
* mergeTwoLists(struct ListNode
* l1
, struct ListNode
* l2
)
{struct ListNode
* cur1
=l1
;struct ListNode
* cur2
=l2
;struct ListNode
* l3
=(struct ListNode
*)malloc(sizeof(struct ListNode
));l3
=NULL;struct ListNode
* cur3
=l3
;if(cur1
==NULL && cur2
!=NULL){return cur2
;}if(cur1
!=NULL && cur2
==NULL){return cur1
;}while(cur1
!=NULL && cur2
!=NULL){if(cur2
->val
<cur1
->val
){if(l3
==NULL){l3
=cur2
;cur3
=l3
;cur2
=cur2
->next
;}else{cur3
->next
=cur2
;cur2
=cur2
->next
;cur3
=cur3
->next
;}}else{if(l3
==NULL){l3
=cur1
;cur3
=l3
;cur1
=cur1
->next
;}else{cur3
->next
=cur1
;cur1
=cur1
->next
;cur3
=cur3
->next
;}}}if(cur3
==NULL){return cur3
;}if(cur1
==NULL){cur3
->next
=cur2
;}if(cur2
==NULL){cur3
->next
=cur1
;}return l3
;
}
六:分割鏈表
此題要注意,要保持相對順序不變,比如4->3->2->1,要把所有小于3的結點放在其余結點之前,那么可以是2->1->4->3,但是決不能是1->2->4->3或2->1->3->4等。
這個題類似于上述合并鏈表,選定題目要求的元素,遍歷此鏈表,小于此元素的尾插到一個鏈表,大于的則尾插到另一個鏈表。但是尾插時會有一個問題,就是新創造的頭指針開始為NULL,尾插時會出現NULL錯誤,所以為了避免這種問題,我們可以創造一個“哨兵”結點,該哨兵結點并不存儲任何有效數據,但是它能使尾插的代碼統一變為"head->next==Newhead"。
class Partition {
public:ListNode
* partition(ListNode
* pHead
, int x
) {struct ListNode
*small_tail
=NULL;struct ListNode
*small_guard
=(ListNode
*)malloc(sizeof(ListNode
));small_guard
->next
=NULL;small_tail
=small_guard
;struct ListNode
*large_tail
=NULL;struct ListNode
*large_guard
=(ListNode
*)malloc(sizeof(ListNode
));large_guard
->next
=NULL;large_tail
=large_guard
;struct ListNode
* cur
=pHead
;while(cur
){if(cur
->val
<x
){small_tail
->next
=cur
;small_tail
=small_tail
->next
;cur
=cur
->next
; }else{large_tail
->next
=cur
;large_tail
=large_tail
->next
;cur
=cur
->next
;}}small_tail
->next
=large_guard
->next
;large_tail
->next
=NULL;return small_guard
->next
;}
};
七:鏈表的回文結構
所謂回文數,就是指正讀和反讀一樣的數字,比如1221。
題目中對于空間復雜度和時間復雜度都有限制,所以就不能使用建立數組的方式進行比較,即使沒有這種限制,采用此方法也是不可取的,因為一旦鏈表過長,時間復雜度將會很大。
我們的思路是,利用之前講過的雙指針法找到中間節點,以中間節點為新的鏈表,對后半部分進行逆置操作,然后進行比較
這里有幾點需要說明
ListNode
* reverse(ListNode
* head
)
{ListNode
* newHead
=NULL;ListNode
* cur
=head
;while(cur
){head
=cur
;cur
=cur
->next
;head
->next
=newHead
;newHead
=head
;}return newHead
;
}class PalindromeList{
public:bool chkPalindrome(ListNode
* A
){ListNode
* slow
=A
;ListNode
* fast
=A
;ListNode
* pre
=NULL;while(fast
->next
){pre
=slow
;fast
=fast
->next
->next
;slow
=slow
->next
;if(fast
==NULL){break;}}pre
->next
=NULL;slow
=reverse(slow
);while(A
){if(A
->val
!=slow
->val
){return false;}else{A
=A
->next
;slow
=slow
->next
;}} return true;}
};
八:相交鏈表
此題最大的障礙就是,兩個鏈表的長度可能不一致,這樣一來就無法比較了。所以可以先分別求出兩個鏈表的長度,接著計算出他們的差距,讓長鏈表先走完這段差距,這樣一樣短的和長的就能同時開始向后走了,一旦出現某個位置相同,那么此位置必然是相交位置
注意下面對于長短鏈表的處理,如果硬要判斷出A長還是B長,那么就要進行分類。所以可以讓一個變量longlist始終保存最長的那個鏈表的地址(也就是先假設再調整)。
struct ListNode
*getIntersectionNode(struct ListNode
*headA
, struct ListNode
*headB
)
{int length_A
=0;int length_B
=0;int gap
=0;struct ListNode
* curA
=headA
;struct ListNode
* curB
=headB
;while(curA
){++length_A
;curA
=curA
->next
;}while(curB
){++length_B
;curB
=curB
->next
;}struct ListNode
* longlist
=headA
;struct ListNode
* shortlist
=headB
;if(length_B
>length_A
){longlist
=headB
;shortlist
=headA
;}gap
=abs(length_A
-length_B
);while(gap
--){longlist
=longlist
->next
;}while(longlist
){if(longlist
==shortlist
)return longlist
;longlist
=longlist
->next
;shortlist
=shortlist
->next
;}return NULL;
}
九:環狀鏈表
可以用快慢指針,讓快指針先走(每次2步),慢指針后走(每次1步),當兩個指針都進入環內時,經過一定次數,一定有快慢指針相等,也就證明有環
也就是此題邏輯為,如果fast某一刻為NULL了,則一定沒有環,但是如果有環,那么fast和slow一定能相遇,從而判斷出來
這里還需要特別注意,快指針必須每次2步,因為如果其他步數,就會存在覆蓋不全的情況,或者說相遇的幾率大大減小,有可能僅僅只差一步,但是fast恰好越過slow,類似于死循環了。
這一點可以證明一下它們一定相遇
bool hasCycle(struct ListNode
*head
)
{struct ListNode
* slow
=head
;struct ListNode
* fast
=head
;while(fast
&& fast
->next
){fast
=fast
->next
->next
;slow
=slow
->next
;if(fast
==slow
)return true;}return false;
}
十:環狀鏈表2
思路一
struct ListNode
*detectCycle(struct ListNode
*head
)
{if(head
== NULL)return NULL;struct ListNode
* slow
=head
;struct ListNode
* fast
=head
;struct ListNode
* cur1
=head
;struct ListNode
* cur2
=NULL;int lengthA
=0;int lengthB
=0;int gap
=0;while(fast
&& fast
->next
){slow
=slow
->next
;fast
=fast
->next
->next
;if(fast
==slow
){break;}}if(fast
==NULL)return NULL;fast
=fast
->next
;slow
->next
=NULL;cur2
=fast
;while(cur1
){++lengthA
;cur1
=cur1
->next
;}while(cur2
){++lengthB
;cur2
=cur2
->next
;}struct ListNode
* longlist
=head
;struct ListNode
* shortlist
=fast
;if(lengthB
>lengthA
){longlist
=fast
;shortlist
=head
;}gap
=abs(lengthA
-lengthB
);while(gap
--){longlist
=longlist
->next
;}while(longlist
){if(longlist
==shortlist
)return longlist
;longlist
=longlist
->next
;shortlist
=shortlist
->next
;}return NULL;}
思路二
這種思路,需要進行證明,且不太好理解,但是代碼十分簡單
struct ListNode
*detectCycle(struct ListNode
*head
)
{struct ListNode
* slow
=head
;struct ListNode
* fast
=head
;while(fast
&& fast
->next
){slow
=slow
->next
;fast
=fast
->next
->next
;if(fast
==slow
)break;} if(fast
==NULL || fast
->next
==NULL)return NULL;struct ListNode
* cur
=head
;while(cur
!=fast
){cur
=cur
->next
;fast
=fast
->next
;}return cur
;
}
十一:復雜鏈表復制
struct Node
* copyRandomList(struct Node
* head
)
{if(head
==NULL){return NULL;}struct Node
* cur
=head
;while(cur
){struct Node
* NewNode
=(struct Node
*)malloc(sizeof(struct Node
));NewNode
->next
=NULL;NewNode
->random
=NULL;NewNode
->val
=cur
->val
;struct Node
* next
=cur
->next
;cur
->next
=NewNode
;NewNode
->next
=next
;cur
=next
;}cur
=head
;while(cur
){struct Node
* behind
=cur
->next
;if(cur
->random
!=NULL)behind
->random
=cur
->random
->next
;elsebehind
->random
=NULL;cur
=cur
->next
->next
;}cur
=head
;struct Node
* returnhead
=head
->next
;while(cur
){struct Node
* NewNode
=cur
->next
;struct Node
* next
=NewNode
->next
;cur
->next
=next
;if(next
==NULL){NewNode
->next
=NULL;break;}NewNode
->next
=next
->next
;cur
=next
;}return returnhead
;}
十二:鏈表插入排序
將一個單鏈表進行插入排序
此題,可以使用一個sorthead保存頭結點,然后剩余結點挨個插入,具體插入時注意以下細節
struct ListNode
* insertionSortList(struct ListNode
* head
)
{if(head
==NULL){return NULL;}struct ListNode
*next
=NULL;struct ListNode
* cur
=NULL;struct ListNode
* sorthead
=head
;head
=head
->next
;從head的下一個結點開始逐個插入sorthead
->next
=NULL;while(head
){if(head
->val
<sorthead
->val
){next
=head
->next
;head
->next
=sorthead
;sorthead
=head
;head
=next
;}else{cur
=sorthead
;while(cur
){if(cur
->next
==NULL){next
=head
->next
;cur
->next
=head
;cur
=cur
->next
;head
=next
;cur
->next
=NULL;break;}if(head
->val
<(cur
->next
)->val
){next
=head
->next
;head
->next
=cur
->next
;cur
->next
=head
;head
=next
;break;}else{cur
=cur
->next
;}}}}return sorthead
;
}
十三:鏈表移除全部重復元素
此題思路不難,但是需要考慮的情況較多,容易寫錯
首先是正常情況1->2->3->3->4->4->5
鏈表由于其特殊性,所以出錯的地方往往就是頭或尾,而本題沒有通過全部用例,也正是因為頭尾這個特殊情況需要特殊處理
特殊情況一1->1->1->3->4
特殊情況2:1-2-3-3-3
最后:
class Solution {
public:ListNode
* deleteDuplication(ListNode
* pHead
){if(pHead
==NULL || pHead
->next
==NULL){return pHead
;}ListNode
* prev
=NULL;ListNode
* cur
=pHead
;ListNode
* next
=cur
->next
;while(next
){if( cur
->val
!= next
->val
){prev
=cur
;cur
=next
;next
=next
->next
;}else{while(next
&& cur
->val
==next
->val
){next
=next
->next
;}if(prev
!=NULL){prev
->next
=next
;}else{pHead
=next
;}cur
=next
;if(next
)next
=cur
->next
;}}return pHead
;}
};
總結
以上是生活随笔為你收集整理的链表经典题的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。