今日任务
- 链表理论基础
- 203.移除链表元素
- 707.设计链表
- 206.反转链表
详细布置
链表理论基础
建议:了解一下链接基础,以及链表和数组的区别
文章链接:代码随想录
单链表
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head
双链表
单链表中的指针域只能指向节点的下一个节点。
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询
循环链表
循环链表,链表首尾相连。循环链表可以用来解决约瑟夫环问题。
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
链表的存储方式
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。
所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
链表的定义
这是因为平时在刷leetcode的时候,链表的节点都默认定义好了,直接用就行了,所以同学们都没有注意到链表的节点是如何定义的。
node ->val调动值
node ->next调动下一个
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
通过自己定义构造函数初始化节点:
ListNode* head = new ListNode(5);
使用默认构造函数初始化节点:
ListNode* head = new ListNode();
head->val = 5;
链表的操作
#删除节点
只要将C节点的next指针 指向E节点就可以了。-----> 在C++里最好是再手动释放这个D节点,释放这块内存。
添加节点
表的增添和删除都是O(1)操作,也不会影响到其他节点。要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)。
链表特性和数组特性比较:
数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。
链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。
203.移除链表元素建议: 本题最关键是要理解 虚拟头结点的使用技巧,这个对链表题目很重要。
题目链接/文章讲解/视频讲解::代码随想录
基本方案:
分两类情况,开头,和剩下部分
每次删链条都得先拿个新的ListNode储存起来,然后,转移,next,在删掉
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//删除头节点
while(head != NULL && head->val == val){
ListNode* tmp = head; //找一个储存旧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;
}
//
};
通过引入虚拟头节点,进行,不需要把头节点区分开,从真是两条开始识别,最后删掉虚拟头节点就行
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//删除头节点
ListNode* feakhead = new ListNode(0);
feakhead->next = head;
ListNode* cur = feakhead;
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 = feakhead->next;
delete feakhead;
return head;
}
//
};
结构体定义:
-
数据组织和封装:结构体允许你将多个不同的数据成员组织在一起,以表示一个复杂的数据结构。在类内部定义结构体可以帮助将相关的数据成员组织在一起,以便更容易地管理和维护。
-
数据抽象:结构体可以用来实现数据抽象,隐藏类的内部实现细节,提供一个公共接口供外部访问。
-
定义蚀刻数据结构:有时,一类可能需要使用复杂的蚀刻
-
提供约束性:使用结构体来表示类内部的数据成员可以提高代码的判断性。通过命名结构体来反映数据成员的关系,可以更清晰地表达类的设计意图。
-
简化代码:有时,一个类可能需要在内部维护
class MyClass {
public:
struct MyStruct {
int value;
double data;
};
// Class methods and other members here...
private:
MyStruct myData;
MyStruct my
// 使用结构体作为类的数据成员
};
MyLinkedList()
初始化MyLinkedList
对象。-
MyLinkedList() { LinkedNode* FeakHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点 Nodesize = 0; }
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- 通过index--刷新node
-
int get(int index) {// 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 //例如index等于3 if(index > (Nodesize - 1)||index < 0){ return -1; } LinkedNode* cur = FeakHead->next; //cur 为0 while(index--){//2 1 0 cur = cur -> next;//cur =1 2 3 } return cur->val; }
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。 将beginnode插到feak和真正的head里边
-
void addAtHead(int val) {//将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。 LinkedNode* beginNode = new LinkedNode(val); beginNode -> next = FeakHead -> next; FeakHead -> next = beginNode; Nodesize++; }
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
-
void addAtTail(int val) { LinkedNode* endNode = new LinkedNode(val); LinkedNode* cur = FeakHead; while(cur->next != nullptr){ cur = cur->next; } cur->next = endNode; Nodesize++; }
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
-
void addAtIndex(int index, int val) { LinkedNode* inneNode = new LinkedNode(val); LinkedNode* cur = FeakHead; if(index>Nodesize){ return; } if(index<0){ index=0; } while(index--){ cur = cur->next; } inneNode -> next = cur -> next; cur->next = inneNode; Nodesize++; }
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
-
void deleteAtIndex(int index) { if(index<0||index>=Nodesize){ return; } LinkedNode* cur = FeakHead; while(index--){ cur = cur->next; } LinkedNode* deleteNode = cur->next; cur->next = cur->next->next; delete deleteNode; deleteNode = nullptr; Nodesize--; }
206.反转链表
题目链接/文章讲解/视频讲解:代码随想录
需要三个初始化,一个pre从Null开始,一个cur,随时提前一步,一个tmp暂存cur,最后返回pre。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* tmp;
ListNode* pre = nullptr;
ListNode* cur = head;
while(cur!=nullptr){
tmp = cur;
cur = cur->next;
tmp->next = pre;
pre = tmp;
}
return pre;
}
};