代码随想录第3天 | 链表 203.移除链表元素,707.设计链表,206.反转链表
题目:203.移除链表元素
思路:
主要是头节点的删除问题,一种是直接在原链表上后移头节点
设置虚拟头结点,指向原链表的头结点,在设置一个cur指针指向当前节点,
虚拟头节点初始化后就不移动了,使用cur进行移动
不要忘记释放删除节点的内存,自行设置的虚拟头节点也要释放
时间复杂度: O(n)
空间复杂度: O(1)
坑:
主要还是指针的理解,是存储地址的变量,只保存地址,地址中内容的改变与该指针无关。
/**
* 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* Dummy=new ListNode(0);//设置虚拟头结点
Dummy->next=head; //
ListNode* cur=Dummy;
while(cur->next!=NULL){
if(cur->next->val==val){ //
ListNode* tmp=cur->next; //c++需要手动释放内存,释放被删除节点的内存空间
cur->next=cur->next->next;
delete tmp;
}else{
cur=cur->next;
}
}
/*对于cur指针和Dummy指针的关系--> B站 bili_guest42 的解释如下:
“指针之间的赋值是值传递的过程,cur和dummy开始时指向同一块节点内存,
在cur没被重新赋值之前,cur的next变化就是dummy的next的变化, 所以dummy的next会指向最新的头节点。”
*/
head=Dummy->next;
delete Dummy;
return head;
}
};
补充:
链表基本知识
一种线性结构,依靠指针进行连接,分为单链表、双向链表(可向前向后查询)和循环链表(首尾相连)
单链表,每个节点由一个数据域和指针域构成,指针指向下一个节点,尾节点指向null(空指针),第一个节点称为head 头节点。
双向链表,每个节点由一个数据域和两个指针域构成,两个指针分别指向前一个节点和后一个节点。
链表在内存中不是连续存储的,是散乱分布在内存中(具体分配机制取决于系统内存管理)
★链表的定义★
面试会考,要会
struct ListNode{
int val;
ListNode *next; //指针是存储地址的变量,声明的类型是告诉编译器该如何解析所存储的地址的
ListNode(int x) :val(x),next(NULL){} /*节点的构造函数,c++系统有一个构默认造函数,
但是初始化的时候不能给变量赋值了,所以自定义构造函数。*/
}
链表初始化
ListNode *phead=new ListNode(0);
ListNode *Dummy=phead->next;
ListNode *tmp=Dummy;
题目:707.设计链表
思路:
设计五个链表的基本操作
- index查找,获取链表中下标为 index 的节点的值
- 头节点前插入,将一个值为 val 的节点插入到链表中第一个元素之前。
- 尾节点后插入,将一个值为 val 的节点追加到链表中作为链表的最后一个元素
- index插入,将一个值为 val 的节点插入到链表中下标为 index 的节点之前。
- 删除index, 如果下标有效,则删除链表中下标为 index 的节点
为了实现 MyLinkedList 类,首先MyLinkedList() 初始化 MyLinkedList 对象
根据要求思考,因为涉及插入删除,所以 设置虚拟头节点 来方便操作
时间复杂度: 涉及 index 的相关操作为 O(index), 其余为 O(1)
空间复杂度: O(n)
坑:
分清cur指向的是 虚拟头结点 or 头结点
我要炸了,试图访问空指针 啊啊啊啊 就硬报错,进行本地调试
addAtTail 有问题!!!!!! 循环判断,cur->next=nullptr 就是到尾部节点了!! 不用for循环遍历len次
成功!!!!吗喽欢呼.gif
class MyLinkedList {
/*编译报错,先定义结构体ListNode,在定义dummyhead
private:
int len;//记录链表的长度
ListNode *dummyhead;//链表虚拟头结点
*/
private:
struct ListNode {
int val;
ListNode* next;
ListNode() :val(0), next(nullptr) {};//参数为空
ListNode(int x) :val(x), next(nullptr) {}
};
ListNode* dummyhead;//链表虚拟头结点
int len;//记录链表的长度
public:
MyLinkedList() {
dummyhead = new ListNode(-1); //创建链表虚拟头结点
len = 0; //链表初始长度为0 值不算虚拟头节点
}
int get(int index) {
if (index<0 || index>len - 1)//错误 不包含虚拟头结点,所以还是len-1
return -1;
ListNode* cur = dummyhead->next; //错误 dummyhead是虚拟头结点,而dummyhead->next是头结点
while (index--) { //任意非零值时都为真
cur = cur->next;
}
return cur->val; //☆☆报错,试图使用空指针,
}
void addAtHead(int val) {
ListNode* newNode = new ListNode(val); //构造函数含参,简化为new ListNode(val)
newNode->next = dummyhead->next;
dummyhead->next = newNode;
len++; //错误 忘记更新len
}
void addAtTail(int val) {
ListNode* newNode = new ListNode(val);
ListNode* cur = dummyhead;
while(cur->next!=nullptr){ // ☆☆☆☆问题在这里!之前用的for循环<len-1次,当len为1时,
cur=cur->next; //导致头结点直接被覆盖掉了
}
cur->next = newNode;
len++;
}
void addAtIndex(int index, int val) {
if (index > len) //错误 索引大于长度直接返回,不是len-1
return; //错误,函数返回类型为void,不用返回-1
if (index == len)
addAtTail(val);
else if (index <= 0) //小于0 在头节点插入
addAtHead(val);
else {
ListNode* newNode = new ListNode(val);
ListNode* cur = dummyhead;
while (index--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
len++;
}
}
void deleteAtIndex(int index) {
if (index < 0 || index >= len)
return;
ListNode* cur = dummyhead;
ListNode* tmp; //保存删除的结点,便于释放
while (index--) {
cur = cur->next;
}
tmp = cur->next;
cur->next = tmp->next;
delete tmp;
tmp = nullptr;
//错误 被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
len--;
}
};
/**
* 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);
*/
补充:
NULL和nullptr
编程指北关于NULL和nullptr的区别
C语言中NULL表示空指针,c++中NULL表示整数0,则在c++语言中使用NULL会产生二义性问题
c++11特性引入了nullptr表示空指针,来解决NULL的问题
在 C++11 及以后的代码中,建议使用 nullptr 代替 NULL 表示空指针。
题目:206.反转链表
思路:
设置虚拟头结点,采用头插法 ,white循环读取每个节点,在从头插入新链表,好的这种方法是对内存的浪费呜呜呜
随想录说直接翻转next
1.双指针法+一个临时指针
时间复杂度: O(n)
空间复杂度: O(1)
2.递归法
根据双指针 写递归法,更容易理解,
先明白递归的结束条件,在找循环部分
时间复杂度: O(n), 要递归处理链表的每个节点
空间复杂度: O(n), 递归调用了 n 层栈空间
坑:
双指针+临时指针
/**
* 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* tmp;
//ListNode* pre=head->next; 报错,试图访问空指针,
ListNode* pre=head; //指向需要反转的结点
ListNode* cur=nullptr; //指向新链表的头结点
while(pre!=nullptr){
tmp=pre->next; //临时保存
pre->next=cur;
cur=pre;
pre=tmp;
}
return cur;
}
};
递归法
/**
* 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* reverse(ListNode *pre, ListNode* cur){ //双指针两个
if(pre==nullptr)
return cur;
else{ //这里没写else 不严谨
ListNode* tmp;
tmp=pre->next;
pre->next=cur;
return reverse(tmp,pre); //报错 没有写 return 没有返回值
}
}
ListNode* reverseList(ListNode* head) {
return reverse(head,nullptr);
}
};
补充:
暂无
今日总结
学习了链表,设计链表,反转链表
理解链表的操作,
分清指向,虚拟头结点还是头结点
尝试写出递归,递归真简洁啊,当然前提是有这个脑子能想明白
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)