題目: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.設計連結串列
思路:
設計五個連結串列的基本操作
- index查詢,獲取連結串列中下標為 index 的節點的值
- 頭節點前插入,將一個值為 val 的節點插入到連結串列中第一個元素之前。
- 尾節點後插入,將一個值為 val 的節點追加到連結串列中作為連結串列的最後一個元素
- index插入,將一個值為 val 的節點插入到連結串列中下標為 index 的節點之前。
- 刪除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);
}
};
補充:
暫無
今日總結
學習了連結串列,設計連結串列,反轉連結串列
理解連結串列的操作,
分清指向,虛擬頭結點還是頭結點
嘗試寫出遞迴,遞迴真簡潔啊,當然前提是有這個腦子能想明白