目录
链表理论基础
鏈表型態
鏈表的存儲
鏈表的定義
鏈表的操作
刪除節點
插入節點
性能分析
讀題
203.移除链表元素
看完代码随想录之后的想法
707.设计链表
自己看到题目的第一想法
看完代码随想录之后的想法
206.反转链表
看完代码随想录之后的想法
203.移除链表元素 - 實作
思路
Code
707.设计链表 - 實作
Code
206.反转链表 - 實作
思路
雙指針
遞迴
Code
雙指針
遞迴
總結
自己实现过程中遇到哪些困难
今日收获,记录一下自己的学习时长
相關資料
链表理论基础
203.移除链表元素
707.设计链表
206.反转链表
鏈表可以想像是一串粽子,粽子(資料),綁粽子的繩子(指標),這兩個形成一個節點,假設有連接兩個粽子之間的繩子則可以當作節點的連結。
鏈表程式定義
struct ListNode{
int val; //數據,這個粽子的內餡是甚麼
ListNode *next; //指標,指向下一個值,是否讓我可以連結下一個粽子的重要關鍵
ListNode(int x): val(x), next(NULL){} //節點的構造函數,就有點像是一開始要不要定義這個粽子是甚麼餡料
}
可不可以有多個數據,就是粽子的內餡可不可以多一點,可以!
但內餡一多,粽子就會變重,道理一樣,如果數據一多,節點就會佔更多空間
struct ListNode{
int val; //數據,這個粽子的內餡是甚麼
char val2;
long val3;
ListNode *next; //指標,指向下一個值,是否讓我可以連結下一個粽子的重要關鍵
ListNode(int x,char y, long z): val(x), val2(y), val3(z), next(NULL){} //節點的構造函數,就有點像是一開始要不要定義這個粽子是甚麼餡料
};
那可不可以不要有構造函數,可以,但在後續操作時,就不能直接賦值給節點了
//有構造函數的:
ListNode* head = new ListNode(1,'2',3);
//無構造函數的
ListNode* head = new ListNode();
head->val = 1;
head->val2 = '2';
head->val3 = 3;
刪除C節點,只要將B節點的指標指向D,並釋放節點C即可
插入E節點,將B節點的指向改向E,並將E節點指向C即可
鏈表跟數組適合不同的情況
資料型態 | 插入/刪除 (時間複雜度) | 查詢(時間複雜度) | 適用場景 |
---|---|---|---|
數組 | $O(n)$ | $O(1)$ | 數據固定 頻繁查詢 較少增刪 |
鏈表 | $O(1)$ | $O(n)$ | 數據不固定 較少查詢 頻繁增刪 |
卡哥主要是講兩種做法
自己看完後,其實就有想法大致上知道怎麼做了,只是對於C++,我目前只是剛學,對於很多語法並不熟悉(看起來要去看黑馬的教程了),所以就去看代碼隨想錄的講義以及影片講解。
自己還是太高估自己了,看完之後就有幾處地方沒有想清楚
並沒有去考慮到非法index的產生
AddAtTail //寫成size-- 實際上應該是cur->next != NULL 這樣寫size會被我改變
tmp=nullptr; //看完代碼隨想錄才知道要再加上這個指針,代碼隨想錄解是如下 //delete命令指示释放了tmp指针原本所指的那部分内存, //被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后, //如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针 //如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
不熟悉語法一開始沒有定義變數
private:
int _size;
LinkedNode* _dummyHead;
看到這細節沒有注意到,的確一步步做出來了,但感覺自己還是會有很大的進步空間,也算釋然吧
一開始看到反轉鏈表的時候,其實有點矇,但聽到卡哥說使用雙指針的方式,並且講解之後,就感覺豁然開朗,在之後說遞迴的寫法就更容易去理解了,對於這個反轉鏈表的實現也比較清晰了
使用兩種做法,一個是沒有使用dummyhead另一個有使用dummyhead
沒有使用dummyhead
錯誤代碼1
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while(head != NULL && head->val == val){
ListNode *tmp = head;
head = head->next;
delete tmp;
}
ListNode *cur = head;
while(cur != NULL && cur->next != NULL){
if(cur->next->val == val){
ListNode *tmp = cur; -> 應該要是cur-next才是我要儲存的
cur->next = cur->next->next;
delete tmp;
}
else{
cur = cur->next;
}
}
return head;
}
};
正確代碼
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while(head != NULL && head->val == val){
ListNode *tmp = head;
head = head->next;
delete tmp;
}
ListNode *cur = head;
while(cur != NULL && cur->next != NULL){
if(cur->next->val == val){
ListNode *tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else{
cur = cur->next;
}
}
return head;
}
};
使用dummyhead
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *dummyhead = new ListNode(0,head);
ListNode *cur = dummyhead;
while(cur != NULL && cur->next != NULL){
if(cur->next->val == val){
ListNode *tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else{
cur = cur->next;
}
}
return dummyhead->next;
}
};
錯誤代碼
class MyLinkedList {
public:
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
MyLinkedList() {
dummyhead = new ListNode(0);
size = 0;
}
int get(int index) {
if (index > (size - 1) || index < 0) {
return -1;
} //一開始並沒有去考慮到非法index的產生
ListNode *cur = dummyhead->next;
while(index -- ){
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
ListNode *newnode = new ListNode(val);
newnode->next = dummyhead->next;
dummyhead->next = newnode;
size++;
}
void addAtTail(int val) {
ListNode *cur = dummyhead;
ListNode *newnode = new ListNode(val);
while(size--){ //寫成size-- 實際上應該是cur->next != NULL 這樣寫size會被我改變
cur = cur->next;
}
size++;
cur->next = newnode;
}
void addAtIndex(int index, int val) {
if(index > size) return;
if(index < 0) index = 0; //一開始並沒有去考慮到非法index的產生
ListNode *cur = dummyhead;
ListNode *newnode = new ListNode(val);
while(index--){
cur = cur->next;
}
newnode->next = cur->next;
cur->next = newnode;
size++;
}
void deleteAtIndex(int index) {
if (index >= size || index < 0) {
return;
}//一開始並沒有去考慮到非法index的產生
ListNode *cur = dummyhead;
while(index--){
cur = cur->next;
}
ListNode *tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
tmp=nullptr; //看完代碼隨想錄才知道要再加上這個指針,代碼隨想錄解是如下
//delete命令指示释放了tmp指针原本所指的那部分内存,
//被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
//如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
size--;
}
private:
int size;
ListNode* dummyhead;
};
正確代碼
class MyLinkedList {
public:
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
MyLinkedList() {
dummyhead = new ListNode(0);
size = 0;
}
int get(int index) {
if (index > (size - 1) || index < 0) {
return -1;
}
ListNode *cur = dummyhead->next;
while(index -- ){
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
ListNode *newnode = new ListNode(val);
newnode->next = dummyhead->next;
dummyhead->next = newnode;
size++;
}
void addAtTail(int val) {
ListNode *cur = dummyhead;
ListNode *newnode = new ListNode(val);
while(cur->next != NULL){
cur = cur->next;
}
cur->next = newnode;
size++;
}
void addAtIndex(int index, int val) {
if(index > size) return;
if(index < 0) index = 0;
ListNode *cur = dummyhead;
ListNode *newnode = new ListNode(val);
while(index--){
cur = cur->next;
}
newnode->next = cur->next;
cur->next = newnode;
size++;
}
void deleteAtIndex(int index) {
if (index >= size || index < 0) {
return;
}
ListNode *cur = dummyhead;
while(index--){
cur = cur->next;
}
ListNode *tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
tmp=nullptr;
size--;
}
private:
int size;
ListNode* dummyhead;
};
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head;
ListNode* pre = NULL;
while(cur){
ListNode* temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};
class Solution {
public:
ListNode* reverse(ListNode* cur, ListNode* pre){
if(cur == NULL) return pre;
ListNode* temp = cur->next;
cur->next = pre;
//對應雙指針法的
//pre = cur;
//cur = temp;
return reverse(temp, cur); //一開始忘記加return
}
ListNode* reverseList(ListNode* head) {
return reverse(head, NULL);
//對應雙指針法的
//ListNode* cur = head;
//ListNode* pre = NULL;
}
};
實現過程中,其實在這三題當中,我很多小細節沒有去考慮到,主要有兩方面
至於困難以及錯誤點都在上面有寫了
自己今天大概學了3hr 左右,整理其實收穫很大,之前有了解過虛擬Head但沒有實際操作過,這一次直接來實行,真的很過癮
另外雙指針法以及遞歸彼此之間的對應,也讓我很欣喜,我很喜歡這樣不斷踏出自己舒適圈一點點的感受,所以認真作筆記以及把自己的錯誤點很誠實地記錄並分享,直面自己的錯誤,對於題目的思考也會更加的清晰。
文章链接:https://programmercarl.com/链表理论基础.html
题目链接/文章讲解/视频讲解::https://programmercarl.com/0203.移除链表元素.html
题目链接/文章讲解/视频讲解:https://programmercarl.com/0707.设计链表.html
题目链接/文章讲解/视频讲解:https://programmercarl.com/0206.翻转链表.html