程式碼隨想錄演算法訓練營第三天 | 203. 移除連結串列元素、 707. 設計連結串列、206.反轉連結串列

apate_清行發表於2024-10-21

連結串列基礎

連結串列分為單連結串列、雙連結串列和迴圈連結串列,連結串列在記憶體中與陣列不同,不是連續儲存的。

C++中連結串列的定義方式如下:

// 單連結串列
struct ListNode {
    int val;  // 節點上儲存的元素
    ListNode *next;  // 指向下一個節點的指標
    ListNode(int x) : val(x), next(NULL) {}  // 節點的建構函式
};

這裡引用程式碼隨想錄卡哥總結的陣列和連結串列的區別:

程式碼隨想錄演算法訓練營第三天 | 203. 移除連結串列元素、 707. 設計連結串列、206.反轉連結串列

6-203.移除連結串列元素

給你一個連結串列的頭節點head 和一個整數val ,請你刪除連結串列中所有滿足Node.val == val 的節點,並返回新的頭節點

示例 1:

輸入:head = [1,2,6,3,4,5,6], val = 6
輸出:[1,2,3,4,5]

示例 2:

輸入:head = [], val = 1
輸出:[]

示例 3:

輸入:head = [7,7,7,7], val = 7
輸出:[]

提示:

  • 列表中的節點數目在範圍 [0, 104]
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

刪除頭節點和非頭節點的操作是不一樣的,這樣會導致程式寫起來較為複雜,首先給出一個使用不同操作來刪除的程式碼:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        while(head != NULL && head->val == val){//刪除頭結點
            ListNode* tmp =  head;//這裡要臨時建立一個tmp以便於後續釋放空間
            head = head -> next;
            delete tmp;//釋放記憶體
        }
        
        ListNode* cur = head;
        while(cur != NULL && cur->next!= NULL){//刪除非頭結點
            if(cur->next->val == val){
                ListNode* tmp = cur->next;//臨時建立一個tmp以便於後續釋放空間
                cur->next = cur->next->next;
                delete tmp;//釋放記憶體
            }else{
                cur = cur->next;
            }
        }
        return head;
    }
};

刪除頭節點,首先判斷頭節點是否為空,如果為空則直接返回,當頭節點指向的值為target的時候,我們刪除頭節點,刪除前記得建立一個臨時指標便於後續釋放空間。

然後就可以定義一個cur來指向目前的頭節點,對後續節點進行操作了。

刪除後續節點,依舊判斷是否為空,為空則返回,不為空且下一節點不為空時,判斷指向的值是否為target,是則刪除當前節點,釋放空間cur向後移動一位,不是則直接向後移動一位。最後返回head即可。

使用虛擬頭節點

使用虛擬頭節點的好處是,當我們使用後,就可以使用統一的判斷邏輯來操作後續所有節點,無需像上述程式碼一樣需要預先判斷是否為頭節點。使用虛擬頭節點的程式碼如下所示:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummyhead= new ListNode(0);
        dummyhead -> next = head;
        ListNode* cur = dummyhead;
        while(cur != NULL && cur->next!= NULL){//刪除結點
            if(cur->next->val == val){
                ListNode* tmp = cur->next;//臨時建立一個tmp以便於後續釋放空間
                cur->next = cur->next->next;
                delete tmp;//釋放記憶體
            }else{
                cur = cur->next;
            }
        }
        head = dummyhead->next;
        delete dummyhead;
        return head;
    }
};

在使用連結串列時,多畫圖可以幫助理解為什麼有些地方指向next更好,有些地方則是直接指向目標。這裡在最後返回時有一個需要注意的點,就是head可能已經被我們刪除了,那麼此時連結串列的頭其實是dummyhead指向的下一個位置。

7-707.設計連結串列

你可以選擇使用單連結串列或者雙連結串列,設計並實現自己的連結串列。

單連結串列中的節點應該具備兩個屬性:valnextval 是當前節點的值,next 是指向下一個節點的指標/引用。

如果是雙向連結串列,則還需要屬性 prev 以指示連結串列中的上一個節點。假設連結串列中的所有節點下標從 0 開始。

實現 MyLinkedList 類:

  • MyLinkedList() 初始化 MyLinkedList 物件。
  • int get(int index) 獲取連結串列中下標為 index 的節點的值。如果下標無效,則返回 -1
  • void addAtHead(int val) 將一個值為 val 的節點插入到連結串列中第一個元素之前。在插入完成後,新節點會成為連結串列的第一個節點。
  • void addAtTail(int val) 將一個值為 val 的節點追加到連結串列中作為連結串列的最後一個元素。
  • void addAtIndex(int index, int val) 將一個值為 val 的節點插入到連結串列中下標為 index 的節點之前。如果 index 等於連結串列的長度,那麼該節點會被追加到連結串列的末尾。如果 index 比長度更大,該節點將 不會插入 到連結串列中。
  • void deleteAtIndex(int index) 如果下標有效,則刪除連結串列中下標為 index 的節點。

示例:

輸入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
輸出
[null, null, null, null, 2, null, 3]

解釋
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2);    // 連結串列變為 1->2->3
myLinkedList.get(1);              // 返回 2
myLinkedList.deleteAtIndex(1);    // 現在,連結串列變為 1->3
myLinkedList.get(1);              // 返回 3

提示:

  • 0 <= index, val <= 1000
  • 請不要使用內建的 LinkedList 庫。
  • 呼叫 getaddAtHeadaddAtTailaddAtIndexdeleteAtIndex 的次數不超過 2000

設計連結串列問題,要求對連結串列有較好的掌握,第一次看還是有點暈,以前完全沒學過連結串列這個型別,遇到了相當的困難,不過還是跟著影片寫下來了。這裡就只貼程式碼了,二刷再複習思路。

class MyLinkedList {
public:
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val), next(nullptr){}
    };
    
    MyLinkedList() {
        _dummyHead = new LinkedNode(0);
        _size = 0;
    }
    
    int get(int index) {
        if (index > (_size - 1) || index < 0) {
            return -1;
        }
        LinkedNode* cur = _dummyHead->next;
        while(index--){ // 如果--index 就會陷入死迴圈
            cur = cur->next;
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        newNode->next = _dummyHead->next;
        _dummyHead->next = newNode;
        _size++;
    }
    // 在連結串列最後面新增一個節點
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = newNode;
        _size++;
    }
    // 在第index個節點之前插入一個新節點,例如index為0,那麼新插入的節點為連結串列的新頭節點。
    // 如果index 等於連結串列的長度,則說明是新插入的節點為連結串列的尾結點
    // 如果index大於連結串列的長度,則返回空
    // 如果index小於0,則在頭部插入節點
    void addAtIndex(int index, int val) {
        if(index > _size) return;
        if(index < 0) index = 0;
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(index--){
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
        _size++;
    }
    
    void deleteAtIndex(int index) {
        if (index >= _size || index < 0) {
            return;
        }
        LinkedNode* cur = _dummyHead;
        while(index--){
            cur = cur->next;
        }
        LinkedNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        tmp = nullptr;
        _size--;
    }
    private:
    int _size;
    LinkedNode* _dummyHead;
};

8-206.反轉連結串列

這題目明天來補,到時候再編輯

相關文章