周五状态不佳、周六公司临时加班,于是周日把周五、周六的任务补上。
链表基础知识
链表基础知识:
203.移除链表元素
题目链接/文章讲解/视频讲解: 代码随想录
这道题是我周五晚上写的,上次接触到链表的时候还是2017年大二的时候。所以周五晚上写的有些吃力。
总体来说确实是非常陌生了,对照着标准答案,每一行代码都吃透了。
该题的两个答案很好的对比了直接使用头结点和使用虚拟头结点的区别。
同时,这道题也让我明白,对链表操作时,不能仅盯着当前结点,要时刻留意下一节点是否为空
方案一、不使用虚拟头结点
/**
* 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) {
// 删除头结点
while (head != nullptr && head->val == val){
ListNode* temp = head;
head = temp->next;
delete temp;
}
// 程序走完第一个while,说明头结点有两种可能:
// ①被删了,说明所有结点数据都等于val
// 此时head,就是null
// ②没被删,说明头结点里的数据一定不等于val
// 当head不是null,才进第二个while
// --------
// 如果head是null,则不进循环,返回的head即null
if (head == nullptr){
return head;
}
// 如果head不是null,暗含着head一定不等于val
// 那么需要从head的下一个开始进行比较
// 如果head之后没有下一个结点,返回的head即为单个结点,不必进循环
// 如果某结点之后没有下一个结点,则不存在下一个结点的比较操作,不必进循环
// 删除操作,是基于某结点的下一个节点进行的
ListNode* curr = head;
while (curr->next != nullptr){
if (curr->next->val == val) {
ListNode* temp = curr->next;
// 把下一节点的下一节点地址,赋给当前节点的下一节点地址
curr->next = curr->next->next;
// 而且,此时当前结点不变
// curr = curr;
delete temp;
}
else{
// 此时需要更新当前节点
curr = curr->next;
}
}
return head;
}
};
方案二、使用虚拟头结点
/**
* 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* virtualHead = new ListNode(0, head);
// 虚拟头结点里的数据,不论是什么,都不参与比较
// 因此也是从下一个节点(真实头结点)开始比较的
ListNode* curr = virtualHead;
while(curr->next != nullptr){
if(curr->next->val == val){
ListNode* temp = curr->next;
curr->next = curr->next->next;
delete temp;
}
else{
// 不要忘了普通结点的更新
curr = curr->next;
}
}
// 不要忘了虚拟头结点和头结点的关系
head = virtualHead->next;
// 不要忘了释放虚拟头结点
delete virtualHead;
return head;
}
};
707.设计链表
这道题非常牛逼!基本上把单链表的所有操作都涵盖了。死去的记忆攻击成功,写的我非常痛苦。
学会了 while( index--) 、 while ( !( curr->next ) ) 这两种写法,区分这两种写法的用途。
复习了构造函数的写法。时刻提醒自己不要忘了计数器。
另外就是,delete之后的是个野指针,一定要记得指针置空,原因:链接
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 < 0 || index >= _size){
return -1;
}
LinkedNode* curr = _dummyHead->next;
while(index--){
curr = curr->next;
}
return curr->val;
}
// 在head前插入
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
_size++;
}
// 在尾部加入
void addAtTail(int val) {
int index = 0;
LinkedNode* curr = _dummyHead;
while (index < _size){
curr = curr->next;
index++;
}
_size++; // 别忘了计数器
LinkedNode* newNode = new LinkedNode(val);
curr->next = newNode;
// 答案
// LinkedNode* curr = _dummyHead;
// LinkedNode* newNode = LinkedNode(val);
// while (curr->next != nullptr){
// curr = curr->next;
// }
// curr->next = newNode;
// _size++;
}
// 在index后加入
void addAtIndex(int index, int val) {
if (index > _size) return;
if (index < 0 ) index = 0; // 这里题目表述不清,为了能通过,与答案保持一致
LinkedNode* curr = _dummyHead;
LinkedNode* newNode = new LinkedNode(val);
while(index--){
curr = curr->next;
}
newNode->next = curr->next;
curr->next = newNode;
_size++; // 别忘了计数器
}
// 删除index后面的
void deleteAtIndex(int index) {
if (index < 0 || index >= _size){
return;
}
LinkedNode* curr = _dummyHead;
while (index--) {
curr = curr->next;
}
LinkedNode* temp = curr->next;
curr->next = temp->next;
// 下面写法没有错,但一开始已经限制了index不会等于_size
// 所以temp不会是nullptr
// if (temp != nullptr) {
// curr->next = temp->next;
// delete temp;
// }
// else{
// curr->next = nullptr;
// }
//delete命令指示释放了tmp指针原本所指的那部分内存,
//被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
//如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
delete temp;
temp = nullptr;
_size--; // 别忘了计数器
}
private:
LinkedNode* _dummyHead;
int _size;
};
/**
* 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);
*/
206.反转链表
这道题刚拿到时没有思路。看到“要先保存下一个指针”这句话后,就有了思路。
有了前两道题的基础,这道题也写的很快。
/**
* 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* curr = head;
ListNode* prev = nullptr;
ListNode* temp = nullptr;
while (curr != nullptr) {
temp = curr->next;
curr->next = prev;
prev = curr;
curr = temp;
}
return prev;
}
};