目录
3.1. LeetCode203 移除链表元素:题意:删除链表中等于给定值 val 的所有节点。
3.2 Leetcode707 设计链表:手撕建立一个单链表
-
链表
-
1.链表基础
什么是链表,链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针)。
特别注意:最后一个节点的指针域指向null(空指针的意思)。
链接的入口节点称为链表的头结点也就是head。
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
数组和链表对比:
2. 链表定义(手撕用)
//单链表(重点记住)
struct ListNode{
int val; //节点上存储的元素
ListNode* next; //指向下一个节点的指针
ListNode(int x):val(x),next(nullptr){} //节点的构造函数
};
//力扣的定义示例:
Day 3:
3.1. LeetCode203 移除链表元素:题意:删除链表中等于给定值 val 的所有节点。
注意:
(1)虚拟头结点的使用:其实可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。
(2)结点删除的内存清理delete
(3) 个人还是习惯用pre和cur配合
//注意与牛客剑指18的区别,那个简单没有重复的数,用那个潜意识参考思路吃大亏了!!
class Solution {
public:
//注意与牛客剑指18的区别,那个简单没有重复的数,用那个潜意识参考思路吃大亏了!!
ListNode* removeElements(ListNode* head, int val) {
ListNode* VirNode=new ListNode(-1);
VirNode->next=head;//虚拟头结点指向头指针
ListNode* cur=head; //cur指针移动
ListNode* pre=VirNode;//pre指针代表cur指针指向元素的前一个元素
while(cur)//只有if 和 else两种情况,至于连续重复的一开始先别管,先把最简单的实现了
{
if(cur->val==val)//如果这个结点符合条件,删除结点,记得清理内存
//不能用while,如果cur最后空了,cur->val报错了,不可能null->val
{
ListNode* temp=cur; //保存地址用以删除,因为cur地址后面改变了
pre->next=cur->next;//只有cur不是NULL,就可以使用cur->next的,若是则会出错,不能NULL->next
cur=cur->next;
//pre->next=cur;//用这行替换上面的那行也行,但要注意前后语句顺序
delete temp;//清理删除结点的内存
}
else //如果不符合,移动pre和cur
{
pre=cur;
cur=cur->next;
}
}
//ListNode* temp=VirNode;//这里其实不用temp再delelte temp也行,因为VirNode以后不用了
head=VirNode->next;//这里因为有可能head移除了
delete VirNode;//记得删除建立的结点
return head;
}
};
3.2 Leetcode707 设计链表:手撕建立一个单链表
注意题目中的链表是怎么初始化的。
注意类中的成员变量是啥:链表长度要不断更新,还有一个虚拟头结点!
class MyLinkedList {
public:
struct ListNode{
int val;
ListNode* next;
ListNode(int x):val(x),next(nullptr){}
};
//初始化,记住这个
MyLinkedList() {
size=0;
dummyNode=new ListNode(-1);
}
//获取index处元素,index从0开始,0代表首元结点
int get(int index) {
if(index<0||index>size-1)
{
return -1;
}
ListNode* cur=dummyNode->next;//首元结点
int count=-1;
while(cur) //使用for循环从首元直接循环(index-1)次也行,老想不起来for,for(int i=1;i<index;i++){cur=cur->next;}
{
count++;//计数
if(count==index)
{
break;
}
cur=cur->next;
}
return cur->val;
}
//头插法
void addAtHead(int val) {
ListNode* p=new ListNode(val);
p->next=dummyNode->next;//先进行这个操作,和下行顺序不能反,要不然后面地址丢失
dummyNode->next=p;
size++; //链表长度加1
}
//尾插法
void addAtTail(int val) {
ListNode* p=new ListNode(val);
ListNode* cur=dummyNode;//注意这里不能写作dummy->next,因为可能一开始不存在这个首元结点,注意!!!
while(cur->next)//找到最后一个元素的位置停止
{
cur=cur->next;
}
cur->next=p;
size++; //链表长度加1
}
//在指定位置index之前插入元素
void addAtIndex(int index, int val) {
if(index>size)
{
return;//把return的if条件语句方法最前面,可以提高运行效率,一开始放到后面了,也能AC
}
else
{
if(index==size)
{
addAtTail(val);//此时不用size++了,因为调用的函数已经加了,废了俩小时才找到错误,血的教训!!
}
else if(index<0)
{
addAtHead(val);//此时不用size++了,因为调用的函数已经加了,废了俩小时才找到错误,血的教训!!
}
else
{
ListNode* p=new ListNode(val);
ListNode* pre=dummyNode;
ListNode* cur=dummyNode->next;
int count=-1;
while(cur)
{
count++;
if(count==index)//找到第index个结点,还有前一个结点
{
break;
}
pre=cur;
cur=cur->next;
}
p->next=cur;//这一行和下一行顺序可互换,因为有双指针pre和cur
pre->next=p;
size++;
}
}
}
//删除第index个元素,从0开始
void deleteAtIndex(int index) {
if(index<0||index>size-1)
{
return;//索引index无效
}
ListNode* pre=dummyNode;
ListNode* cur=dummyNode->next;
int count=-1;
while(cur)
{
count++;
if(count==index)
{
pre->next=cur->next;
delete cur;//这里可以直接删除cur,因为不用了
size--;
break;
}
pre=cur;
cur=cur->next;
}
}
private:
int size;//定义一个链表长度
ListNode* dummyNode;//定义一个虚拟头结点,用于初始化
};
3.3 反转链表:力扣
注意:pre和cur指针的配合,还是习惯各种用pre和cur,跟代码随想录题解思路不太一致
//思路都有,一写都是浆糊,移动pre指针老弄不对,因为多写了个虚拟头结点,pre指向它了,这里根本不用,要指向NULL
class Solution {
public:
//思路都有,一写都是浆糊,移动pre指针老弄不对,因为多写了个虚拟头结点,pre指向它了,这里根本不用,要指向NULL
ListNode* reverseList(ListNode* head) {
ListNode* pre=nullptr;//这里pre注意要写成nullptr空指针
ListNode* cur=head;
while(cur)//遍历链表
{
ListNode* temp=cur->next;//先保存cur指向右边的下一个元素,放到外面毛定义也可
cur->next=pre;//cur反向指向pre(指向左边)。这里不能写反,是要给cur->next赋值,改变cur的指向
//如果是pre=cur->next那相当于改变pre,使pre指向cur的下一个结点了,如第一步中那就是pre指向2
//更新pre和cur指针
pre=cur;//改变pre的指向,右移一个元素
cur=temp;//cur右移一个元素
}
return pre;
}
};