程式碼隨想錄第3天 | 連結串列 203.移除連結串列元素,707.設計連結串列,206.反轉連結串列

跳圈發表於2024-06-07

題目:203.移除連結串列元素

思路:

主要是頭節點的刪除問題,一種是直接在原連結串列上後移頭節點
設定虛擬頭結點,指向原連結串列的頭結點,在設定一個cur指標指向當前節點,
虛擬頭節點初始化後就不移動了,使用cur進行移動
不要忘記釋放刪除節點的記憶體,自行設定的虛擬頭節點也要釋放
時間複雜度: O(n)
空間複雜度: O(1)

坑:

主要還是指標的理解,是儲存地址的變數,只儲存地址,地址中內容的改變與該指標無關。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* Dummy=new ListNode(0);//設定虛擬頭結點
        Dummy->next=head;               //
        ListNode* cur=Dummy;  
        while(cur->next!=NULL){
            if(cur->next->val==val){   //
                ListNode* tmp=cur->next;       //c++需要手動釋放記憶體,釋放被刪除節點的記憶體空間
                cur->next=cur->next->next;
                delete tmp;
            }else{
                cur=cur->next;
            }
        }
        /*對於cur指標和Dummy指標的關係--> B站 bili_guest42 的解釋如下:
          “指標之間的賦值是值傳遞的過程,cur和dummy開始時指向同一塊節點記憶體,
            在cur沒被重新賦值之前,cur的next變化就是dummy的next的變化, 所以dummy的next會指向最新的頭節點。”    
        */
        head=Dummy->next;
        delete Dummy;
        return head;
    }
};

補充:

連結串列基本知識
一種線性結構,依靠指標進行連線,分為單連結串列、雙向連結串列(可向前向後查詢)和迴圈連結串列(首尾相連)
單連結串列,每個節點由一個資料域和指標域構成,指標指向下一個節點,尾節點指向null(空指標),第一個節點稱為head 頭節點。
雙向連結串列,每個節點由一個資料域和兩個指標域構成,兩個指標分別指向前一個節點和後一個節點。
連結串列在記憶體中不是連續儲存的,是散亂分佈在記憶體中(具體分配機制取決於系統記憶體管理)
★連結串列的定義★
面試會考,要會

struct ListNode{
    int val;
    ListNode *next;  //指標是儲存地址的變數,宣告的型別是告訴編譯器該如何解析所儲存的地址的
    ListNode(int x) :val(x),next(NULL){}  /*節點的建構函式,c++系統有一個構預設造函式,
                                             但是初始化的時候不能給變數賦值了,所以自定義建構函式。*/
}

連結串列初始化
ListNode *phead=new ListNode(0);
ListNode *Dummy=phead->next;
ListNode *tmp=Dummy;

題目:707.設計連結串列

思路:

設計五個連結串列的基本操作

  1. index查詢,獲取連結串列中下標為 index 的節點的值
  2. 頭節點前插入,將一個值為 val 的節點插入到連結串列中第一個元素之前。
  3. 尾節點後插入,將一個值為 val 的節點追加到連結串列中作為連結串列的最後一個元素
  4. index插入,將一個值為 val 的節點插入到連結串列中下標為 index 的節點之前。
  5. 刪除index, 如果下標有效,則刪除連結串列中下標為 index 的節點

為了實現 MyLinkedList 類,首先MyLinkedList() 初始化 MyLinkedList 物件
根據要求思考,因為涉及插入刪除,所以 設定虛擬頭節點 來方便操作
時間複雜度: 涉及 index 的相關操作為 O(index), 其餘為 O(1)
空間複雜度: O(n)

坑:

分清cur指向的是 虛擬頭結點 or 頭結點

我要炸了,試圖訪問空指標 啊啊啊啊 就硬報錯,進行本地除錯
addAtTail 有問題!!!!!! 迴圈判斷,cur->next=nullptr 就是到尾部節點了!! 不用for迴圈遍歷len次
成功!!!!嗎嘍歡呼.gif

class MyLinkedList {
/*編譯報錯,先定義結構體ListNode,在定義dummyhead
	private:
		int len;//記錄連結串列的長度
		ListNode *dummyhead;//連結串列虛擬頭結點
	*/
private:
	struct ListNode {
		int val;
		ListNode* next;
		ListNode() :val(0), next(nullptr) {};//引數為空
		ListNode(int x) :val(x), next(nullptr) {}
	};
	ListNode* dummyhead;//連結串列虛擬頭結點
	int len;//記錄連結串列的長度

public:
	MyLinkedList() {
		dummyhead = new ListNode(-1); //建立連結串列虛擬頭結點
		len = 0;   //連結串列初始長度為0  值不算虛擬頭節點
	}

	int get(int index) {
		if (index<0 || index>len - 1)//錯誤 不包含虛擬頭結點,所以還是len-1
			return -1;
		ListNode* cur = dummyhead->next; //錯誤  dummyhead是虛擬頭結點,而dummyhead->next是頭結點
		while (index--) {   //任意非零值時都為真
			cur = cur->next;
		}
		return cur->val;   //☆☆報錯,試圖使用空指標,
	}
	void addAtHead(int val) {
		ListNode* newNode = new ListNode(val); //建構函式含參,簡化為new ListNode(val)
		newNode->next = dummyhead->next;
		dummyhead->next = newNode;
		len++;  //錯誤 忘記更新len
	}

	void addAtTail(int val) {
		ListNode* newNode = new ListNode(val);
		ListNode* cur = dummyhead;
		while(cur->next!=nullptr){ // ☆☆☆☆問題在這裡!之前用的for迴圈<len-1次,當len為1時,
              cur=cur->next;                            //導致頭結點直接被覆蓋掉了
        }
		cur->next = newNode;
		len++;
	}

	void addAtIndex(int index, int val) {
		if (index > len) //錯誤  索引大於長度直接返回,不是len-1
			return; //錯誤,函式返回型別為void,不用返回-1
		if (index == len)
			addAtTail(val);
		else if (index <= 0)  //小於0 在頭節點插入
			addAtHead(val);
		else {
			ListNode* newNode = new ListNode(val);
			ListNode* cur = dummyhead;
			while (index--) {
				cur = cur->next;
			}
			newNode->next = cur->next;
			cur->next = newNode;
			len++;
		}
	}

	void deleteAtIndex(int index) {
		if (index < 0 || index >= len)
			return;
		ListNode* cur = dummyhead;
		ListNode* tmp;  //儲存刪除的結點,便於釋放
		while (index--) {
			cur = cur->next;
		}
		tmp = cur->next;
		cur->next = tmp->next;
		delete tmp;
		tmp = nullptr;
		//錯誤 被delete後的指標tmp的值(地址)並非就是NULL,而是隨機值。也就是被delete後,
		//如果不再加上一句tmp=nullptr,tmp會成為亂指的野指標
		len--;
	}
};

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */

補充:

NULL和nullptr
程式設計指北關於NULL和nullptr的區別
C語言中NULL表示空指標,c++中NULL表示整數0,則在c++語言中使用NULL會產生二義性問題
c++11特性引入了nullptr表示空指標,來解決NULL的問題

在 C++11 及以後的程式碼中,建議使用 nullptr 代替 NULL 表示空指標。

題目:206.反轉連結串列

思路:

設定虛擬頭結點,採用頭插法 ,white迴圈讀取每個節點,在從頭插入新連結串列,好的這種方法是對記憶體的浪費嗚嗚嗚
隨想錄說直接翻轉next
1.雙指標法+一個臨時指標
時間複雜度: O(n)
空間複雜度: O(1)
2.遞迴法
根據雙指標 寫遞迴法,更容易理解,
先明白遞迴的結束條件,在找迴圈部分
時間複雜度: O(n), 要遞迴處理連結串列的每個節點
空間複雜度: O(n), 遞迴呼叫了 n 層棧空間

坑:

雙指標+臨時指標

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* tmp;
        //ListNode* pre=head->next;  報錯,試圖訪問空指標,
        ListNode* pre=head;     //指向需要反轉的結點
        ListNode* cur=nullptr;  //指向新連結串列的頭結點
        while(pre!=nullptr){
            tmp=pre->next;   //臨時儲存
            pre->next=cur;
            cur=pre;
            pre=tmp;
        }
        return cur;
    }
};

遞迴法

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
//先定義遞迴函式
    ListNode* reverse(ListNode *pre, ListNode* cur){  //雙指標兩個
        if(pre==nullptr) 
            return cur;
        else{    //這裡沒寫else 不嚴謹
            ListNode* tmp;
            tmp=pre->next;
            pre->next=cur;
            return reverse(tmp,pre); //報錯 沒有寫 return 沒有返回值
        }
    }
    ListNode* reverseList(ListNode* head) {
        return reverse(head,nullptr);
    }
};

補充:

暫無

今日總結

學習了連結串列,設計連結串列,反轉連結串列
理解連結串列的操作,
分清指向,虛擬頭結點還是頭結點
嘗試寫出遞迴,遞迴真簡潔啊,當然前提是有這個腦子能想明白

相關文章