一、移除链表元素:
这也是我看到该题的第二次,我对卡哥讲的还是有些印象,但是我想试试没有虚拟头结点的方式来解决这道题目
无虚拟头节点:
ListNode* removeElements(ListNode* head, int val) {
// 移除头节点符合的节点
while ( head != NULL && head->val == val) {
head = head->next;
}
// 定义一个临时节点,来做头结点的替代品
ListNode *a = head;
// 开始查找
while (a != NULL && a->next != NULL) {
if (a->next->val == val) {
ListNode* temp = a->next;
a->next = a->next->next;
delete temp;
} else {
// 遍历下一个
a = a->next;
}
}
return head;
}
我在写题目的时候,没有加上两个while循环的head != NULL and a != NULL 的条件,我在问完GPT的之后,还是出现报错,我问了半天发现,必须把这两个条件写在while条件的前面才可以运行成功,这种程序保护机制太厉害了!
接下来是
虚拟头节点版本:
ListNode* removeElements(ListNode* head, int val) {
// ListNode * cur = new ListNode(0); // 如果直接这样,就会导致内存外泄
// cur->next = head; // 定义虚拟头节点
// 定义虚拟头节点
ListNode *dummy = new ListNode(0);
dummy->next = head;
ListNode *cur = dummy;
while (cur != NULL && cur->next != NULL) {
if (cur->next->val == val) {
ListNode * temp = cur->next;
cur->next = cur->next->next;
delete temp; // 释放删除后的元素的内存
} else {
cur = cur->next;
}
}
head = dummy->next; // 返回更新后的链表的头节点
delete dummy; // 释放内存,防止溢出
return head;
}
在编写的时候还是出了一些问题,在定义虚拟头节点的时候,我要一开始是直接ListNode* cur->next = head; 导致出现了错误,因为这个节点没有定义,所以错误了,然后就是在最后的head赋值(其实可以直接返回dummy->next),我并没有把dummy->next的节点赋值给head,导致head还是指向那个原来的链表,导致报错。
二、设计链表
我拿到这个题目和我第一次拿到这个题目有着不一样的感觉,那就是我会定义链表了...(好像也没有什么区别),大概花了一个小时才蒙蔽的的写完了。。之间出了很多错误,总结一下有以下几点:
(1)忘记定义链表的结构体。
(2)没有在函数外定义虚拟头节点和size的对象,导致做出的全是局部变量
(3)没有弄清楚cur(临时节点)指的dummy节点的位置,到底是dummy、还是dummy->next
(4)没有确定边界条件,size和index的大小。
写完之后,还是很懵逼,决定看一下卡哥的视频。
1、版本1
class MyLinkedList {
public:
int size;
struct LinkNode {
int val;
LinkNode* next;
LinkNode(int val) : val(val), next(nullptr) {} // 构造函数,初始化链表
};
LinkNode* _dummyHead;
MyLinkedList() {
_dummyHead = new LinkNode(0); // 定义一个虚拟头节点
size = 0; // 初始化长度
}
int get(int index) {
if (index < 0 || index >= size) return -1;
LinkNode* cur = _dummyHead->next; // 定义一个临时节点,来遍历指针
//index--; // 下标是从0开始,所以需要维护一下
while (index--) {
cur = cur->next;
}
return cur->val;
}
// 在头节点之前插入
void addAtHead(int val) {
LinkNode* a = new LinkNode(val); // 创建一个新的节点
// 利用虚拟节点,插入新的节点
a->next = _dummyHead->next;
_dummyHead->next = a;
size++;
}
// 在尾部插入节点
void addAtTail(int val) {
LinkNode* a = new LinkNode(val); // 创建一个新的节点
LinkNode* cur = _dummyHead; // 临时节点
// 遍历到尾部
while (cur->next != nullptr) {
cur = cur->next;
}
cur->next = a; // 在尾部添加节点
size++;
}
// 在指定位置添加节点
void addAtIndex(int index, int val) {
if (index < 0 || index > size) return;
LinkNode* a = new LinkNode(val);
LinkNode* cur = _dummyHead; // 临时节点,便于遍历
//index--;
// 搜索位置
while (index--) {
cur = cur->next;
}
// 添加
a->next = cur->next;
cur->next = a;
size++;
}
// 删除指定位置的节点
void deleteAtIndex(int index) {
if (index < 0 || index >= size) return;
LinkNode* cur = _dummyHead;
//index--;
while (index--) {
cur = cur->next;
}
LinkNode* a = cur->next;
cur->next = cur->next->next;
delete a; // 删除缓存
size--;
}
};
卡个视频总结:
(1)在执行get()时,只要想好极端条件,一切都变得清晰。
(2)在头插时,一定要先确定插入结点的next再进行其他操作。
(3)尾插法,确定什么时候到了尾部
(4)再第n个节点之前插入节点,和get()一样,想好极端条件即可
(5) 删除的操作也相同
(6)一定要记得维护size的长度
看完之后,我重新写了一遍
2、版本二
class MyLinkedList {
public:
// 构造链表结构体,使用虚拟头节点
struct LinkNode{
int val;
LinkNode* next;
// 重构函数
LinkNode(int val) : val(val), next(nullptr) {}
};
// 定义虚拟头节点和size成员
int size;
LinkNode* dummyHead;
// 初始化函数
MyLinkedList() {
size = 0;
dummyHead = new LinkNode(0);
}
// 找节点
int get(int index) {
if (index < 0 || index > size - 1) return -1;
LinkNode* cur = dummyHead->next;
while (index--) {
cur = cur->next;
}
return cur->val;
}
// 在头节点之前插入
void addAtHead(int val) {
LinkNode* a = new LinkNode(val);
LinkNode* cur = dummyHead;
a->next = cur->next;
cur->next = a;
size++;
}
// 在尾部插入节点
void addAtTail(int val) {
LinkNode* a = new LinkNode(val);
LinkNode* cur = dummyHead;
while (cur->next != nullptr) {
cur = cur->next;
}
//a->next = cur->next;
cur->next = a;
size++;
}
// 在指定位置添加节点
void addAtIndex(int index, int val) {
// 这里的边界条件需要微微的修改,因为插入有可能是为节点NULL的缘故
if (index < 0 || index > size) return;
LinkNode* a = new LinkNode(val);
LinkNode* cur = dummyHead;
while (index--) {
cur = cur->next;
}
a->next = cur->next;
cur->next = a;
size++;
}
// 删除指定位置的节点
void deleteAtIndex(int index) {
if (index < 0 || index > size - 1) return;
LinkNode* cur = dummyHead;
while (index--) {
cur = cur->next;
}
LinkNode* a = cur->next;
cur->next = cur->next->next;
delete a; // 释放内存
size--;
}
};
这里的按位置插入的边界有一点点的区别;
三、链表反转
看到这个题目,我隐隐约约感觉好像是双指针,又看了一下卡哥的文章,确实是双指针,但是还有一些细节,
1、在while的时候,因为right是要遍历到Null去所以条件是right != NULL,还有返回值是Left,这个时候left才是头节点
代码:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* left = nullptr;
ListNode* right = head;
while (right != nullptr) {
ListNode* tmp = right->next;
right->next = left;
left = right;
right = tmp;
}
// 返回的是left
return left;
}
};
今天满课,学习时间,两个半小时,继续加油!感谢卡哥!