文章链接:链表基础知识、203.移除链表元素、707.设计链表、206.反转链表
视频链接:203.移除链表元素、707.设计链表、206.反转链表
题目链接:203.移除链表元素、707.设计链表、206.反转链表
链表学习
今天是第一次正式的学习链表代码的书写,之前大一上学期去图书馆看过有关“数据结构”的书籍,但没有看用C++代码写的“数据结构”,所以可以说今天的对链表的学习是从小白开始的。
以下是根据代码随想录所作的简单思维导图:
清晰版本(幕布网址):https://mubucm.com/doc/1GVSbM1VVCu
看完答案后的想法
203.移除链表元素
移除链表元素有两种办法:一、直接使用原来的链表进行移除节点操作;二、设置一个虚拟头节点再进行移除节点操作
// 方法一:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 删除头节点
while(head != NULL && head->val == val){
// 头节点不为空且数据域的元素为目标元素
// 注意不是if
ListNode* tmp = head;
head = head->next; // 更新头节点:头节点指向原头节点的下一个数据的地址
delete tmp; // 删除原头节点,释放内存
}
// 删除非头节点
ListNode* cur = head; // cur为head别名
while(cur != NULL && cur->next != NULL){
// 头节点不为空且其下一个节点不为空
// 注意不是if
if(cur->next->val == val){
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp; // 释放内存
}
else{
cur = cur->next; // 更新cur
}
}
return head; // 返回头节点
}
};
// 方法二:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头节点
// new(): 分配这种类型的一个大小的内存空间,并以括号中的值来初始化这个变量;
dummyHead->next = head; // 虚拟头节点指向head
ListNode* cur = dummyHead; // 定义一个临时指针进行遍历链表
while(cur->next != NULL){
if(cur->next->val == val){
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else{
cur = cur->next;
}
}
head = dummyHead->next;
delete dummyHead; // 记得释放内存
return head; // 返回头节点
}
};
707.设计链表
这道题是自己实现一个链表,其中包括五个函数:
零、定义链表节点结构体/初始化链表
// 定义链表节点结构体
struct LinkedNode{
int val;
LinkedNode* next;
LinkedNode(int val):val(val), next(nullptr){}
}; // 别忘了分号
// 初始化链表
MyLinkedList() {
dummyhead = new LinkedNode(0); //定义一个虚拟头节点
l_size = 0;
}
一、获取指定节点的值
// 获得指定节点的值
int get(int index) {
if(index > (l_size - 1) || index < 0){ // 下标从0开始
return -1;
}
LinkedNode* cur = dummyhead->next;
while(index--){ // 不能写--index
cur = cur->next;
}
return cur->val;
}
二、头部插入节点
// 头部插入节点
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = dummyhead->next; // 两个不能互换位置
dummyhead->next = newNode;
l_size++;
}
三、尾部插入节点
// 尾部插入节点
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
// 查询操作:找链表原先的最后一个元素
LinkedNode* cur = dummyhead;
while(cur->next != nullptr){
cur = cur->next;
}
cur->next = newNode; // 不用再将newNode的指针域指向NULL,因为本来就指向NULL
l_size++;
}
四、指定节点前插入节点
// 指定节点前插入节点
void addAtIndex(int index, int val) {
if(index > l_size) return;
if(index < 0) index = 0; // index小于0,则在头部插入节点
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = dummyhead;
while(index--){
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
l_size++;
}
五、删除指定节点
// 删除指定节点
void deleteAtIndex(int index) {
if(index > (l_size - 1) || index < 0){
return;
}
LinkedNode* cur = dummyhead;
while(index--){
cur = cur->next; // 第n个节点是cur->next
}
LinkedNode* tmp = cur->next; // 用于释放内存
cur->next = cur->next->next;
delete tmp;
tmp = NULL; // 避免tmp成为野指针
l_size--;
}
最后、打印链表
// 打印链表
void printLinkedList(){
LinkedNode* cur = dummyhead;
while(cur->next != NULL){
cout << cur->next->val << " ";
cur = cur->next;
}
cout << '\n';
}
完整代码:
class MyLinkedList {
public:
// 定义链表节点结构体
struct LinkedNode{
int val;
LinkedNode* next;
LinkedNode(int val):val(val), next(nullptr){}
}; // 别忘了分号
// 初始化链表
MyLinkedList() {
dummyhead = new LinkedNode(0); //定义一个虚拟头节点
l_size = 0;
}
// 获得指定节点的值
int get(int index) {
if(index > (l_size - 1) || index < 0){ // 下标从0开始
return -1;
}
LinkedNode* cur = dummyhead->next;
while(index--){ // 不能写--index
cur = cur->next;
}
return cur->val;
}
// 头部插入节点
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = dummyhead->next; // 两个不能互换位置
dummyhead->next = newNode;
l_size++;
}
// 尾部插入节点
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
// 查询操作:找链表原先的最后一个元素
LinkedNode* cur = dummyhead;
while(cur->next != nullptr){
cur = cur->next;
}
cur->next = newNode; // 不用再将newNode的指针域指向NULL,因为本来就指向NULL
l_size++;
}
// 指定节点前插入节点
void addAtIndex(int index, int val) {
if(index > l_size) return;
if(index < 0) index = 0; // index小于0,则在头部插入节点
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = dummyhead;
while(index--){
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
l_size++;
}
// 删除指定节点
void deleteAtIndex(int index) {
if(index > (l_size - 1) || index < 0){
return;
}
LinkedNode* cur = dummyhead;
while(index--){
cur = cur->next; // 第n个节点是cur->next
}
LinkedNode* tmp = cur->next; // 用于释放内存
cur->next = cur->next->next;
delete tmp;
tmp = NULL; // 避免tmp成为野指针
l_size--;
}
// 打印链表
void printLinkedList(){
LinkedNode* cur = dummyhead;
while(cur->next != NULL){
cout << cur->next->val << " ";
cur = cur->next;
}
cout << '\n';
}
private:
int l_size;
LinkedNode* dummyhead;
};
/**
* 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.反转链表
两种方法:双指针法 和 递归法
双指针法
// 双指针法
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 临时指针tmp用于保存cur的下一个节点
ListNode* temp;
// 初始化
ListNode* cur = head;
ListNode* pre = NULL;
while(cur){ // cur指向NULL时终止循环
temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};
递归法
// 递归法
class Solution {
public:
ListNode* reverse(ListNode* pre, ListNode* cur){ // 注意函数定义(返回的时指针)
if(cur == NULL) return pre;
ListNode* temp = cur->next;
cur->next = pre;
return reverse(cur, temp);
}
ListNode* reverseList(ListNode* head) {
return reverse(NULL, head);
}
};
遇到困难
203.移除链表元素
为什么方法二(设置一个虚拟头节点)定义完虚拟头节点后要再定义临时指针(cur)进行遍历?
解答:因为如果用头指针进行遍历,那么头指针是一直在改变的;而用临时指针时,头指针是不会改变的。
707.设计链表
定义结构体时最后少了分号,卡了好久……好傻。
注意下标从哪里开始。
刚开始不知道为什么在deleteAtIndex()函数中要设立一个临时指针tmp,最后知道是为了释放内存。
206.反转链表
双指针法中,因为cur->next会改变,所以要用到临时指针temp用于保存cur->next。
递归法中, 对函数的定义不太熟悉。
今日收获
今天用4个小时深入学习了链表的知识,知道了链表的定义及其相关操作。