代码随想录算法训练营第三天 | LeetCode203.移除链表元素、LeetCode707.设计链表、LeetCode206.反转链表
01 链表理论基础
- ref:链表:离散内存空间
- 要点:单链表,双链表和循环链表
单链表的C++实现方式:
struct ListNode{ int val; //节点上存储的元素 ListNode* next; //指向下一个节点的指针 ListNode(int x): val(x),next(NULL){} //节点的构造函数 }
创建节点
ListNode* head = new ListNode(5);
02-1 LeetCode203.移除链表元素
相关资源
题目链接:https://leetcode.cn/problems/remove-linked-list-elements/
文章讲解:移除链表元素
视频讲解:https://www.bilibili.com/video/BV18B4y1s7R9/
题目:给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回新的头节点 。
第一想法:之前学数据结构的时候了解过虚拟头节点的概念,因此直接采用这种思路完成本题
实现:最初写的代码如下:
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) {
//创建了虚拟头节点newHead(其实应该用dummyHead)
ListNode* newHead = new ListNode(0, head);
//创建指针nowHead指向当前节点(其实应该用cur)
ListNode* nowHead = newHead;
//需要注意的是链表要想删除元素,需要立足于前一个元素
while (nowHead->next != nullptr) {
if (nowHead->next->val == val) {
nowHead->next = nowHead->next->next;
}
else {
nowHead = nowHead->next;
}
}
return newHead->next;
}
};
看完代码随想录之后的想法: 我是因为之前有先验知识才直接采用了虚拟头节点去做,其实应该尝试不采用这种方式,对删除头节点和内部节点分类处理,然后感受一下虚拟头节点的优势
收获:熟悉c++的链表结构体写法,重拾虚拟头节点概念
ToDo:尝试不用虚拟头节点实现,且需要规范代码命名,如dummyHead、cur;尝试递归解法
02-2 LeetCode707.设计链表
相关资源
题目链接:https://leetcode.cn/problems/design-linked-list/
文章讲解:设计链表
视频讲解:https://www.bilibili.com/video/BV1FU4y1X7WD/
题目:
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val
和 next
。val
是当前节点的值,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
的节点。
第一想法:之前用c语言学数据结构的时候实现过,按理说c++岂不是信手拈来
实现:真正实现的时候由于c++不够熟练,花了相当长的时间去实现算法,但其实思路和代码随想录一致。
#include<iostream>
using namespace std;
class MyLinkedList {
public:
int val;
MyLinkedList* dummyHead = nullptr;
MyLinkedList* next = nullptr;
MyLinkedList() {
dummyHead = new MyLinkedList(0); // 创建一个虚拟头结点
dummyHead->next = nullptr;
dummyHead->val = 0;
}
MyLinkedList(int value) : val(value), next(nullptr) {}
int get(int index) {
MyLinkedList* cur = dummyHead;
if (index < 0) {
return -1;
}
while (index >= 0) {
if (cur->next != nullptr) {
cur = cur->next;
index--;
}
else {
return -1;
}
}
return cur->val;
}
void addAtHead(int val) {
MyLinkedList* newNode = new MyLinkedList();
newNode->val = val;
if (dummyHead->next != nullptr) {
newNode->next = dummyHead->next;
}
dummyHead->next = newNode;
return;
}
void addAtTail(int val) {
MyLinkedList* cur = this->dummyHead;
MyLinkedList* newNode = new MyLinkedList();
newNode->val = val;
while (cur->next != nullptr) {
cur = cur->next;
}
cur->next = newNode;
return;
}
void addAtIndex(int index, int val) {
MyLinkedList* cur = dummyHead;
MyLinkedList* newNode = new MyLinkedList();
newNode->val = val;
if (index == 0) {
addAtHead(val);
}
while (index > 0) {
if (cur->next != nullptr) {
cur = cur->next;
index--;
}
else {
return;
}
}
newNode->next = cur->next;
cur->next = newNode;
return;
}
void deleteAtIndex(int index) {
MyLinkedList* cur = dummyHead;
if (index < 0) {
return;
}
if (index == 0 ) {
if (cur->next == nullptr) { return; }
else {
cur->next = cur->next->next;
}
}
while (index > 0) {
if (cur->next != nullptr) {
cur = cur->next;
index--;
}
else {
return;
}
}
cur->next = cur->next->next;
return;
}
};
int main() {
MyLinkedList* obj = new MyLinkedList();
obj->addAtHead(1);
obj->addAtTail(3);
obj->addAtIndex(1,2);
cout << obj->get(1) << endl;
obj->deleteAtIndex(1);
cout << obj->get(1) << endl;
}
遇到的问题:
- 不知道结构体也可以作为类的成员变量,导致代码写得很混乱,类MyLinkedList既维护链表又作为节点,直接导致初始化混乱,这样写甚至出现过递归错误,后续给了一个出口解决:
class MyLinkedList {
public:
int val;
MyLinkedList* dummyHead;
MyLinkedList* next;
MyLinkedList() {
dummyHead = new MyLinkedList(); // 创建一个虚拟头结点
dummyHead->next = nullptr;
dummyHead->val = 0;
}
- 在本地Visual Studio能够正常跑通,但是放到LeetCode上却出现以下诡异错误:
搜索发现LeetCode对野指针敏感,因此代码应该对指针进行初始化
MyLinkedList* dummyHead = nullptr;
MyLinkedList* next = nullptr;
看完代码随想录之后的想法:思路一致,但是c++的熟练程度没有跟上
收获
-
结构体可作为类内成员变量
-
LeetCode提交代码对指针进行初始化,严防野指针
ToDo:重构代码
02-3 LeetCode206.反转链表
相关资源
题目:给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
第一想法:第一眼感觉题目很简单嘛,一下子就想到重新创建一个链表,然后一次遍历就可以实现翻转。但我总觉得这样浪费了空间,并且题目还说了可以用递归的方式去实现,因此花了近四十分钟去想递归的解法,之前算法课我觉得已经通透地掌握了递归,也就是把解法建立在递归的函数之上,但这道题一直没想出来咋递归而且看了代码随想录的递归之后还是百思不得其解,信心受挫!
实现:通过新创建链表实现
#include <iostream>
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) {
if (head == nullptr) {
return head;
}
ListNode* reverseHead = nullptr;
while (head != nullptr) {
ListNode* newNode = new ListNode(head->val, reverseHead);
reverseHead = newNode;
head = head->next;
}
return reverseHead;
}
};
遇到的问题:在新链表的头指针初始化想了会,灵光乍现采用nullptr
看完代码随想录之后的想法: 代码随想录提供的改变链表的next指针的指向,直接将链表反转的算法很有启发,在此基础上完成了递归的算法,但我一直无法理解这种递归,感觉像是为了递归而递归
收获:翻转链表的双指针解法,无需重新创建链表
ToDo:实现双指针解法、递归算法