06 |「链表」必刷题
前言
前言:刷「鏈表」高頻面試題。
文章目錄
- 前言
- 一. 基礎回顧
- 1. 增刪改查
- 2. 虛擬頭節點
- 1)頭節點
- 2)為什么需要虛擬頭結點
- 3. 鏈表的遍歷
- 二. 高頻面試題
- 1. 例題
- 例題1:LeetCode 206 反轉鏈表
- 1)題目鏈接
- 2) 算法思路
- 3)源碼剖析
- 4)時間復雜度
- 例題2:LeetCode 92 反轉鏈表II
- 1)題目鏈接
- 2) 算法思路
- 3)源碼剖析
- 4)時間復雜度
- 例題3:LeetCode 203 移除鏈表元素
- 1)題目鏈接
- 2)遍歷做法
- 3)遞歸做法
- 3)時間復雜度
- 2. 習題
- 習題1:LeetCode 19 刪除鏈表的第N個節點
- 1)題目鏈接
- 2) 算法思路
- 3)源碼剖析
- 3)時間復雜度
- 習題2:LeetCode 876 鏈表的中間節點
- 1)題目鏈接
- 2) 算法思路
- 3)源碼剖析
- 3)時間復雜度
- 習題3:LeetCode 160 相交鏈表
- 1)題目鏈接
- 2) 算法思路
- 3)源碼剖析
- 3)時間復雜度
- 習題4:LeetCode 141 環型鏈表
- 1)題目鏈接
- 2) 算法思路
- 3)源碼剖析
- 4)時間復雜度
- 習題5:LeetCode 142. 環形鏈表 II
- 1)題目鏈接
- 2) 算法思路
- 3)源碼剖析
- 4)時間復雜度
- 習題6:LeetCode 234 回文鏈表
- 1)題目鏈接
- 2) 算法思路
- 3)源碼剖析
- 4)時間復雜度
- 習題7:LeetCode 21 合并兩個有序鏈表
- 1)題目鏈接
- 2) 算法思路
- 3)源碼剖析
- 4)時間復雜度
一. 基礎回顧
詳細介紹參考 04 講: 鏈表簡析(點擊鏈接直達)
1. 增刪改查
結構:111->222->333->NULL,鏈表是 指向型結構 。
查找:隨機訪問的時間復雜度是 O(n)O(n)O(n)。
增刪:刪除和插入元素的時間復雜度都是 O(1)O(1)O(1) 。
2. 虛擬頭節點
1)頭節點
對于鏈表,給我們一個鏈表時,我們拿到的是頭節點(head) 。如果沒有頭結點證明整個鏈表為空 NULL,如果已有頭結點則證明鏈表不為空。
2)為什么需要虛擬頭結點
針對鏈表頭結點 (head)為空和不為空需要執行不同的操作。每次對應頭結點都需要單獨處理,所以使用頭結點的技巧,可以解決這個問題。
例如,刪除鏈表中的某個節點必須要找到前一個節點才能操作。這就造成頭結點的尷尬,對于頭結點來說沒有前一個節點。
如果鏈表為空(head = null),那么 訪問 null.val 與 null.next 會出錯。為了避免這種情況,增加一個虛擬頭結點(dummy)可以統一操作,不用關心頭結點是否為空。這樣 dummy.next = null,避免直接訪問空指針。
其中 dummy 的值 (val)常用 -1 表示,next 指向 頭結點(head)。
3. 鏈表的遍歷
// 增加虛擬頭節點的鏈表遍歷 dummy; dumm->next = head; p = dummy; while (p) {} // 沒有虛擬頭結點的鏈表遍歷 head; while (head) {head = head->next; }二. 高頻面試題
1. 例題
例題1:LeetCode 206 反轉鏈表
1)題目鏈接
原題鏈接:反轉鏈表(點擊鏈接直達)
2) 算法思路
- 明確:修改幾條邊,修改哪幾條邊,注意是修改 n 條邊;
- 操作:將當前節點的 next 指針改為指向前一個節點(last);
- 維護:雙鏈表可以通過 pre 指針訪問前一個節點。針對單鏈表,沒有 pre 指針無法訪問前一個節點(last),需要新開一個變量維護前一個節點(last);
- 邊界:針對頭結點(head)沒有前一個節點,創建 last 并賦為 NULL;
3)源碼剖析
class Solution { public:ListNode* reverseList(ListNode* head) {if (!head || !head->next) return head;ListNode* last = nullptr; //(1)ListNode* cur = head; //(2)while (cur) //(3){ListNode* next = cur->next; //(4)cur->next = last; //(5)last = cur; //(6)cur = next; //(7)}return last; //(8)} };- (1)/(2)初始化變量 last 和 cur, last 指向上一個節點,cur 指向當前節點;
- (3)修改每條邊,需要循環遍歷訪問每個節點;
- (4)修改一條邊時,先保存當前節點(cur)的下一個節點(next),防止丟失;
- (5)修改一條邊;
- (6)/(7)last 和 cur 分別向后移動一位;
- (8)返回反轉后鏈表的頭結點。當 cur 停下時指向原鏈表的 NULL,此時 last 指向反轉后鏈表的頭結點;
4)時間復雜度
????????O(n)O(n)O(n)
例題2:LeetCode 92 反轉鏈表II
1)題目鏈接
原題鏈接:反轉鏈表(點擊鏈接直達)
2) 算法思路
- 將 tmp 節點移動到 left-1 的位置處;
- 反轉 [left, right] 部分的節點。從 left 位置開始反轉,反轉 right-left 次;
- 調整剩余部分節點的指向;
- 返回頭結點;
3)源碼剖析
class Solution { public:ListNode* reverseBetween(ListNode* head, int left, int right) {if (left == right) return head; //(1)ListNode* dummy = new ListNode(-1); //(2)dummy->next = head;ListNode* tmp = dummy;for (int i = 0; i < left - 1; i ++) tmp = tmp->next; //(3)//(4) ListNode* pre = tmp->next;ListNode* cur = pre->next;for (int i = 0; i < right - left; i ++) {ListNode* next = cur->next;cur->next = pre;pre = cur;cur = next;}//(5) tmp->next->next = cur;tmp->next = pre;return dummy->next; //(6)} };- (1)left=right 證明只有一個頭結點;
- (2)dummy 為哨兵節點。因為 left 可能在 head 位置,故添加哨兵節點;
- (3)將 tmp 節點移動到 left-1 的位置;
- (4)(4)- (5)之間的代碼為反轉 [left, right] 部分的節點,邏輯同上題;
- (5)(5)-(6) 之間的代碼為調整其它節點的指向。如示例1,2 的 next 指向 5,1的 next 指向 4;
- (6)返回鏈表頭節點;
4)時間復雜度
????????O(n)O(n)O(n)
例題3:LeetCode 203 移除鏈表元素
1)題目鏈接
原題鏈接:移除鏈表元素(點擊鏈接直達)
2)遍歷做法
- 增加 dummy 哨兵節點的目的是統一操作,少寫特判斷頭結點(head)是否為空。// 不增加哨兵節點dummy if (!head) {return head; } else {} // 增加哨兵節點dummy class Solution { public:ListNode* removeElements(ListNode* head, int val) {ListNode* dummy = new ListNode(-1);dummy->next = head;ListNode* p = dummy;while (p->next){if (p->next->val == val) p->next = p->next->next;else p = p->next;}return dummy->next;} };
3)遞歸做法
if (!head) return head; head->next = removeElements(head->next, val); return head->val == val? head->next : head;3)時間復雜度
????????O(n)O(n)O(n)
2. 習題
習題1:LeetCode 19 刪除鏈表的第N個節點
1)題目鏈接
原題鏈接: 刪除鏈表的第N個節點(點擊鏈接直達)
2) 算法思路
紙上畫圖實際模擬一遍即可。
3)源碼剖析
class Solution { public:ListNode* removeNthFromEnd(ListNode* head, int n) {ListNode* dummy = new ListNode(-1); //(1)dummy->next = head;ListNode* p = dummy, *q = dummy;for (int i = 0; i < n; i ++) p = p->next; //(2)while (p->next != nullptr) //(3){p = p->next;q = q->next;}q->next = q->next->next; //(4)return dummy->next;} };- (1)定義虛擬頭結點 dummy,不用考慮頭結點的特殊情況;
- (2)p 指針先走 n 步;
- (3)p 指針和 q 指針同時走,直到 p 指針走到最后一個節點,兩指針都停下;
- (4)此時 q 指向的就是要刪除節點的前一個節點(n-1處),刪除第 n 個節點;
3)時間復雜度
????????雙指針遍歷時間復雜度為 O(n)O(n)O(n)
習題2:LeetCode 876 鏈表的中間節點
1)題目鏈接
原題鏈接: 鏈表的中間節點(點擊鏈接直達)
2) 算法思路
- 模擬枚舉。奇數個節點, q 走到中點時,p->next為NULL。偶數個節點,q 走到中點時,fast為空 NULL。
3)源碼剖析
class Solution { public:ListNode* middleNode(ListNode* head) {auto p = head, q = head;while (p && p->next) { // 只要p和p->next都不為空時,兩指針就一種往后走p = p->next->next;q = q->next;}return q;} };3)時間復雜度
????????雙指針遍歷時間復雜度為 O(n)O(n)O(n)
習題3:LeetCode 160 相交鏈表
1)題目鏈接
原題鏈接: 相交鏈表(點擊鏈接直達)
2) 算法思路
- 判斷相交:兩指針是否相等;
- 難點:兩個鏈表相同節點前面的長度不同,無法控制遍歷的長度。
例如,鏈表 a: 1=>2=>3=>4,鏈表 b:5=>3=>4,相同節點為 3。對于3 前面的鏈表部分,兩個鏈表長度不同; - 解決:將兩個鏈表邏輯上拼接在一起。先遍歷鏈表 a,遍歷完后再遍歷鏈表 b。同理,先先遍歷鏈表 b,遍歷完后再遍歷鏈表 a。這樣,相同節點前面的長度就保持一致了,可以通過遍歷相同的次數走到相同的節點;
例如,鏈表 a 邏輯上變為:1=>2=>3=>4=>5=>3=>4,鏈表 b 邏輯上變為:5=>3=>4=>1=>2=>3=>4;
3)源碼剖析
class Solution { public:ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {auto p = headA, q = headB;while (p != q) {// p沒走到A鏈表終點就一直往后走,走到終點就開始走B鏈表p = p != NULL? p->next : headB;// q沒走到B鏈表終點就一直往后走,走到終點就開始走A鏈表q = q != NULL? q->next : headA;}return p;} };3)時間復雜度
???????? O(n)O(n)O(n)
習題4:LeetCode 141 環型鏈表
1)題目鏈接
原題鏈接: 環型鏈表(點擊鏈接直達)
2) 算法思路
- 明確什么叫有環;
- 明確有環和無環的區別:
- 定義:fast 是跑得快的指針,slow是跑的慢的指針??熘羔樏看巫邇刹?#xff0c;慢指針每次走一步;
- 有環:有環相當于 fast 和 slow 兩指針在環形操場跑,如果 fast 和 slow 相遇,那一定是 fast 超過了 slow 一圈;
- 無環:無環相當于 fast 和 slow 兩指針在直道操場跑,因為快指針跑的快會先達到終點,則兩指針一定不會遇到;
3)源碼剖析
class Solution { public:bool hasCycle(ListNode *head) {ListNode* slow = head, *fast = head;while (fast && fast->next) //(1){fast = fast->next->next; //(2)slow = slow->next; //(3)if (fast == slow) return true; //(4)}return false; //(5)} };- (1)判斷快指針是否到達終點;
- (2)快指針每次走兩步;
- (3)慢指針每次走一步;
- (4)兩指針相遇,證明兩指針套圈了,則一定有環;
- (5)快指針先達到終點,證明無環;
4)時間復雜度
???????? O(n)O(n)O(n)
習題5:LeetCode 142. 環形鏈表 II
1)題目鏈接
原題鏈接: 環形鏈表 II(點擊鏈接直達)
2) 算法思路
- 本題在上題的基礎上增加了新需求。除了判斷是否有環,還需要返回入環節點的索引;
- 定義兩個指針,一個是 fast,一個是 slow, fast一次走兩步,slow一次走一步;
- 先讓兩指針相遇
- fast指針走過的路程:a+b+n×圈a + b + n×圈a+b+n×圈,圈=b+c圈 = b + c圈=b+c,得出 a+b+n(b+c)a + b + n(b + c)a+b+n(b+c);
- slow指針走過的路程 a+ba + ba+b;
- 根據時間相等: a+ba + ba+b = (a+b+n×(b+c))/2(a + b + n × (b + c)) / 2(a+b+n×(b+c))/2 ? ? ?公式①;
- 相遇后找入口節點
- 當兩指針相遇之后,一個指針從頭結點 head出發,另一個指針從相遇點出發,兩指針以相同速度走,直到相遇為止,相遇的點就是鏈表環的入口節點;
- 將公式① 等式兩邊消掉一個 a+ba + ba+b,得到 a+ba + ba+b = $n × (b + c)) ,得到 aaa = n×(b+c)?bn × (b + c) - bn×(b+c)?b,因為是環形的,aaa = (n?1)×(b+c)+c(n - 1) × (b + c) + c(n?1)×(b+c)+c;
- 圖注:| 表示入環節點的位置,a 表示從起點出發到入環節點位置的路程,b 表示從入環節點的位置到相遇節點位置的路程,c 表示從相遇節點位置到入環節點位置的路程,* 表示兩指針相遇節點的位置
3)源碼剖析
class Solution { public:ListNode *detectCycle(ListNode *head) {if (!head || !head->next) return NULL;ListNode* slow = head, *fast = head;while (fast && fast->next) {slow = slow->next;fast = fast->next->next;if (slow == fast) {ListNode* cur = head;while (cur != slow){cur = cur->next;slow = slow->next;}return cur;}}return NULL;} };4)時間復雜度
???????? O(n)O(n)O(n)
習題6:LeetCode 234 回文鏈表
1)題目鏈接
原題鏈接: 回文鏈表(點擊鏈接直達)
2) 算法思路
- 鏈表不能向數組一樣直接通過索引找到鏈表的中點,需要從頭節點挨個遍歷;
- 找鏈表的中點(參考習題2,“LeetCode 876 鏈表的中間節點” 的講解),找中點遍歷時,同時將中點的前半段進行翻轉
- 鏈表長度分奇數和偶數,如果 fast 指針沒有指向 null,說明鏈表長度為奇數,slow 還要再向前一步;
- 再依次遍歷這中點兩邊的兩段鏈表,依次對比是否相同;
3)源碼剖析
class Solution { public:bool isPalindrome(ListNode* head) {ListNode* slow = head, *fast = head;ListNode* pre = nullptr;while (fast != nullptr && fast->next != nullptr){fast = fast->next->next;ListNode* next = slow->next;slow->next = pre;;pre = slow;slow = next;}if (fast != nullptr) slow = slow->next; // 如果fast沒有指向null,說明鏈表長度為奇數,slow還要再往前走一步while (pre && slow){if (pre->val != slow->val) return false;pre = pre->next;slow = slow->next;}return true;} };4)時間復雜度
???????? O(n)O(n)O(n)
習題7:LeetCode 21 合并兩個有序鏈表
1)題目鏈接
原題鏈接: 合并兩個有序鏈表(點擊鏈接直達)
2) 算法思路
二路歸并
3)源碼剖析
class Solution { public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {auto p = list1, q = list2;auto dummy = new ListNode(-1);auto cur = dummy;while (p && q){if (p->val <= q->val) {cur->next = p;p = p->next;cur = cur->next;}else{cur->next = q;q = q->next;cur = cur->next;}}if (p) cur->next = p;if (q) cur->next = q;return dummy->next;} };4)時間復雜度
???????? O(n)O(n)O(n)
總結
以上是生活随笔為你收集整理的06 |「链表」必刷题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一篇超频菜鸟必看的基础知识大全!
- 下一篇: SIO有关知识