Skip to content
Rain Hu's Workspace
Go back

[Algo] 0-3. 鏈表(Linked List)

Rain Hu

一、鏈表的基本結構

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

二、鏈表的基本操作

1. 查(Read)

int at(ListNode* head, int n){      // index 為 n
    ListNode* curr = head;
    while (n--){        // 移動 n 次
        curr = curr->next;
    }
    return curr->val;
}

2. 改(Update)

void update(ListNode* head, int n, int val){
    ListNode* curr = head;
    while (n--){
        curr = curr->next;
    }
    curr->val = val;      // 查完後賦值
}

3. 增(create)

ListNode* insert(ListNode* head, int n, int val){
    if (!head) return NULL;                             // 處理當鏈表為空的狀態
    if (n == 0){                                        // 處理當插入位置為 0 時的特例
        ListNode* newHead = new ListNode(val, head);
        head = newHead;
        return head;
    }
    ListNode* curr = head;
    while (curr && --n){                                // 移動到插入位置的前一位
        curr = curr->next;
    }
    ListNode* tmp = curr->next;                         // 預先存下來插入位置的後一位
    curr->next = new ListNode(val);                     // 插入元素
    curr->next->next = tmp;                             // 將元素的下一位指派給存下來的後一位
    return head;
}
ListNode* insert(ListNode* head, int n, int val){
    if (!head) return NULL;
    ListNode* dummy = new ListNode(-1, head);           // 創建一個 dummy head
    ListNode* curr = dummy;
    while (curr && n--){                                // 注意為 n-- 
        curr = curr->next; 
    }
    ListNode* tmp = curr->next;
    curr->next = new ListNode(val);
    curr->next->next = tmp;
    return dummy->next;                                 // 注意為返回 dummy 的下一位
}
void insert(ListNode* node, int val){
    int tmp = node->val;
    node->val = val;
    node->next = new ListNode(tmp, node->next);
}

4. 減(delete)

ListNode* erase(ListNode* head, int n, int val){
    if (!head) return NULL;
    ListNode* dummy = new ListNode(-1, head);
    ListNode* curr = dummy;
    while (curr && n--){
        curr = curr->next; 
    }
    curr->next = curr->next->next;      // 將前一位的後一位指給後一位
    return dummy->next;
}

[LeetCode. 237] Delete Node in a Linked List(Medium)

void insert(ListNode* node, int val){
    node->val = node->next->val;        // 將當前的值賦值成下一位的值
    node->next = node->next->next;      // 將下一個節點刪除
}

三、鏈表的進階操作

1. 刪值

void remove(ListNode* head, int target){
    ListNode* prev = NULL;
    ListNode* curr = head;
    while (curr && curr->val != target){
        prev = curr;
        curr = curr->next;
    }
    if (!prev)                  // 處理例外
        head = head->next;
    else
        prev->next = prev->next->next;
}

2. 建表

ListNode* build(vector<int> nums){
    ListNode* dummy = new ListNode(-1);
    ListNode* prev = dummy;
    ListNode* curr = NULL;

    for (int i = 0; i < nums.size(); i++){
        curr = new ListNode(nums[i]);
        prev->next = curr;
        prev = curr;
        curr = curr->next;
    }
    ListNode* head = dummy->next;
    delete(dummy);
    return head;
}

3. 鏈表的後序遍歷

void traverse(ListNode* head){
    // pre-order
    traverse(head->next);
    // post-order
}
void removeAll(ListNode* head, int target){
    ListNode* dummy = new ListNode(-1, head);
    ListNode* curr = dummy;
    while (curr && curr->next){
        if (curr->next->val == target){         // 當前節點的下一位符合 target 則刪除它
            curr->next = curr->next->next;
        } else {
            curr = curr->next;                  // 否則則繼續後下遍歷
        }
    }
}
void removeAll(ListNode* head, int target){
    if (!head) return;                          // 假如鏈表為空,則退出函式
    while (head && head->val == target){
        if (head->next){
            head->val = head->next->next;
            head->next = head->next->next;      // 刪除
        } else {
            head = NULL;                        // 當最後一個元素需移除時
        }
    }
    removeAll(head->next, target)               // 前序遍歷
}
void removeAll(ListNode* head, int target){
    int tmp;
    if (head->next) 
        tmp = removeAll(head->next, target)     // recursion
    if (tmp == target)                          // 後序遍歷可以取得下一位的回傳的值       
        head->next = head->next->next;          // 有了需要刪除的節點的前一位,要刪除就容易啦!
    return head->val                            // 傳回當前節點的值
}

四、秀一波的操作

1. 刪值

void remove(ListNode* head, int target){
    ListNode** curr = &head;    // 將指向指針的 curr 指向 head
    while ((*curr)->val != target)
        curr = &(*curr)->next;
    if (!(*curr)) return;       // 避免掉指向 NULL
    *curr = (*curr)->next
}

2. 建表

ListNode* build(vector<int> nums){
    ListNode* head = new ListNode(nums[0]);
    ListNode** curr = &head;
    for (int i = 0; i < nums.size(); i++){
        (*curr)->next = new ListNode(nums[i]);
        curr = &(*curr)->next;
    }
}

五、鏈表的演算法

1. 反轉鏈表

[LeetCode. 206] Reverse Linked List(Easy)

ListNode* reverse(ListNode* head){
    ListNode* prev = NULL;
    ListNode* curr = head;
    ListNode* next = NULL;
    while (curr){
        next = curr->next;      // 先記住下一個位置
        curr->next = prev;      // 將指針指向前一位,以達成反轉的目的
        prev = curr;            // 往前移動
        curr = next;            // 往前移垂
    }
    return prev;
}
ListNode* reverse(ListNode* head){
    if (!head || head->next) return head;   // 處理終止條件
    ListNode last = reverse(head->next);    // post-order traversal:回傳已排序好的子鏈表,並傳回最後一項
    head->next->next = head;
    head->next = NULL;
    return last;
}

[[Followup] 反轉前 N 個節點

ListNode* successor = NULL;
ListNode* reverseN(ListNode* head, int n){
    if (n == 1){                            // 只反轉 1 個節點相當於沒有反轉,故轉回自己
        successor = head->next;             // 記錄反轉後的鏈表要接到哪裡->剩餘鏈表的頭
        return head;
    }
    ListNode last = reverseN(head->next, n-1);
    head->next->next = head;
    head->next = successor;                 // 最後將鏈表的尾巴接到剩餘鏈表的頭
    return last;
}

[LeetCode. 92] Reverse Linked List II(Medium)

ListNode* reverseBetween(ListNode* head, int m, int n){
    if (m == 1){
        return reverseN(head, n);                       // 與 LeetCode.92 一樣
    }
    head->next = reverseBetween(head->next, m-1, n-1);  // 前進到 base case
    return head;
}

[LeetCode. 25] Reverse Nodes in k-Group (Hard)

ListNode* reverseKGroup(ListNode* head, int k) {
    ListNode* curr = head;
    int cnt = 0;
    while (curr && cnt < k){
        curr = curr->next;
        cnt++;
    }
    if (cnt == k){              // 當節數小於 k 時,不做 reverse
        curr = reverseKGroup(curr, k);  // 傳回的是 reverse 完的鏈表的 head,故需把 reverse 完的尾與之相接
        while (cnt-- > 0){
            ListNode* next = head->next;
            head->next = curr;
            curr = head;
            head = next;
        }
        return curr;            // 當節數等於 k 時回傳的是尾巴
    }
    return head;                // 注意節數小於 k 時仍回傳 head
}

2. 環型鏈表(龜兔賽跑-快慢指針)

[LeetCode. 141] Linked List Cycle(Easy)

bool hasCycle(ListNode *head) {
    ListNode* fast = head;
    ListNode* slow = head;
    while (fast && fast->next){             // 確保快指針與他的下一位都不是 NULL
        fast = fast->next->next;            // 快指針走兩步
        slow = slow->next;                  // 慢指針走一步
        if (fast == slow) return true;      // 若兩者相撞,則必有環
    }
    return false;
}

[LeetCode. 142] Linked List Cycle II(Medium)

ListNode *detectCycle(ListNode *head) {
    ListNode* fast = head;
    ListNode* slow = head;
    while (fast && fast->next){
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow) break;                // 若有環則退出
    }   
    if (!fast || !fast->next) return NULL;      // 若快指針已經走到底表示沒有環
    fast = head;                              // 讓其中一個指針從頭開始走,並一同樣的速度走
    while (fast != slow){                     // 相遇點即為相交點
        fast = fast->next;
        slow = slow->next;
    }
    return fast;
}

[LeetCode. 876] Middle of the Linked List(Easy)

ListNode* middleNode(ListNode* head) {
    ListNode* fast = head;
    ListNode* slow = head;
    while (fast && fast->next){
        fast = fast->next->next;
        slow = slow->next;
    }   
    return slow;
}

3. 雙指針(前後指針)

[LeetCode. 19] Remove Nth Node From End of List(Medium)

ListNode* removeNthFromEnd(ListNode* head, int n) {
    // 注意以下我們要刪除第 n 個節點,故我們需找第 n-1 個節點,為避免刪除第一個節點的例子,我們引入 dummy
    ListNode* dummy = new ListNode(-1, head);  
    ListNode* slow = dummy;
    ListNode* fast = dummy;
    while (fast && n--){                // 前指針先行走 n 個節點
        fast = fast->next;
    }
    while (fast->next){                 // 保持等速
        slow = slow->next;
        fast = fast->next;
    }
    slow->next = slow->next->next;      // 刪除第 n 個節點

    return dummy->next;
}

[LeetCode. 160] Intersection of Two Linked Lists(Easy)

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    ListNode* a = headA;
    ListNode* b = headB;
    bool flagA = true;          // 用來標記是否已接過另一鏈表
    bool flagB = true;          // 用來標記是否已接過另一鏈表
    while (a && b){
        if (a == b) return a;   // 相遇表示相交點
        a = a->next;
        b = b->next;
        if (!a && flagA){
            a = headB;
            flagA = false;      // 已接過另一鏈表
        } 
        if (!b && flagB){
            b = headA;
            flagB = false;      // 已接過另一鏈表
        } 
    }
    return NULL;
}

[LeetCode. 86] Partition List(Medium)

ListNode* partition(ListNode* head, int x) {
    ListNode* dummy1 = new ListNode(-1);
    ListNode* dummy2 = new ListNode(-1);
    ListNode* curr1 = dummy1;
    ListNode* curr2 = dummy2;
    while (head){
        if (head->val < x){
            curr1->next = head;
            curr1 = curr1->next;
        } else {
            curr2->next = head;
            curr2 = curr2->next;
        }
        head = head->next;
    }
    curr1->next = dummy2->next;
    curr2->next = NULL;
    return dummy1->next;
}

[LeetCode. 21] Merge Two Sorted Lists(Easy)

ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
    ListNode* dummy = new ListNode(-1);
    ListNode* curr = dummy;
    while (list1 && list2){
        if (list1->val <= list2->val){
            curr->next = list1;
            list1 = list1->next;
        } else {
            curr->next = list2;
            list2 = list2->next;
        }
        curr = curr->next;
    }
    curr->next = list1 ? list1 : list2;
    return dummy->next;
}

4. 優先佇列

[LeetCode. 23] Merge k Sorted Lists(Hard)

ListNode* mergeKLists(vector<ListNode*>& lists) {
    auto cmp = [](ListNode* a, ListNode* b){return a->val > b->val;};
    priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> pq(cmp);
    ListNode* dummy = new ListNode(-1);
    ListNode* curr = dummy;
    for (ListNode* node : lists){
        if (node) pq.push(node);
    }
    while (!pq.empty()){
        ListNode* node = pq.top();
        pq.pop();
        curr->next = node;
        curr = curr->next;
        node = node->next;
        if (node) pq.push(node);
    }
    return dummy->next;
}


Share this post on:

Previous
[Algo] 0-4. 二叉樹(Binary Tree)
Next
[Algo] 0-2. 算法思維