日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

06 |「链表」必刷题

發布時間:2023/12/20 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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是跑的慢的指針。快指針每次走兩步,慢指針每次走一步;
    • 有環:有環相當于 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 |「链表」必刷题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。