代码随想录算法训练营第3天|203.移除链表元素、707.设计链表、206.反转链表
LeetCode203
2025-01-24 10:50:39 星期五
题目描述:力扣203
文档讲解:代码随想录(programmercarl)203.移除链表元素
视频讲解:《代码随想录》算法视频公开课:链表基础操作| LeetCode:203.移除链表元素
代码随想录视频内容简记
原链表删除元素
删除链表中间的某个元素,这样的方式就需要分别对头结点和不是头结点的情况进行讨论
梳理
-
如果头结点的value是target值,则删除头结点
-
如果中间结点的value是target值,删除中间结点
-
返回头指针head
大致代码内容
-
第一种情况,target是头结点的值
while (head != NULL && head -> val == target)
则执行head = head -> next
。另外就是这里之所以要用while就是为了防止出现[1,1,1,1]诸如此类的情况 -
第二种情况,target是中间结点的值。首先需要定义一个
current
指针指向头结点。也就是cur = head
`while (cur != NULL && cur -> next != NULL){`
if (cur -> next -> val == target){
cur -> next = cur -> next -> next;
}
}
- 最后该删的都删完了
return head
使用虚拟头结点
使用虚拟头结点的好处就是处理规则是统一的,就不用再分情况了
梳理
-
首先定义虚拟头结点
-
中间循环遍历进行删除
-
返回虚拟头结点指向的下一个结点。注意这里一定是下一个结点,不是初始的head
大致代码内容
-
ListNode* dummyHead = new ListNode(0);
-
中间的循环遍历如下,注意中间仍让要定义一个新的临时指针用来指代
cur = dummyHead
while (cur -> next != NULL ) {
if (cur -> next -> val == target){
cur -> next = cur -> next -> next;
}
}
- 返回
return dummyHead -> next;
LeetCode测试
原链表删除元素
代码比较好通过,就是要分清楚分别删除头结点和中间结点使用不同的条件判断
这里还有一个小坑,就是如果要在代码中放delete结点的部分,ListNode* cur = head
这行判断就必须放在删除完头结点部分的下面,否则有可能就把cur定义的head删掉了
点击查看代码
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while (head != NULL && head->val == val){
ListNode* tmp = head;
head = tmp->next;
delete tmp;
}
ListNode* cur = head;
while (cur != NULL && cur->next != NULL){
if (cur->next->val == val){
ListNode* tmp = cur->next;
cur->next = tmp->next;
delete tmp;
}
else {
cur = cur->next;
}
}
return head;
}
};
使用虚拟头结点
这个就是要注意仍然要额外引入一个cur指针,最后返回dummyHead -> next
就可以
点击查看代码
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode(0);
ListNode* cur;
dummyHead -> next = head;
cur = dummyHead;
while (cur -> next != NULL){
if (cur -> next -> val == val){
ListNode* tmp = cur -> next;
cur -> next = tmp -> next;
delete tmp;
}
else cur = cur -> next;
}
return dummyHead -> next;
}
};
LeetCode707
题目描述:力扣707
文档讲解:代码随想录(programmercarl)707.设计链表
视频讲解:《代码随想录》算法视频公开课:帮你把链表操作学个通透!LeetCode:707.设计链表
代码随想录视频内容简记
在函数编写的时候统一用虚拟头结点的方式,方便进行增删查改
初始化链表
这部分就是两个工作
-
直接新建一个
dummyHead
指针。我想这里长度为0的话,就默认dummyHead为第一个结点了,值域为什么不是head
而是dummyHead
,我感觉仅仅只是写法的问题,其实并没有dummyHead和Head同时存在 -
size赋0
获取第n个结点的值
-
使用
cur = dummyHead->next;
指向虚拟头结点的下一个位置 -
循环如下
while (n--) {
cur = cur->next;
}
在头部插入结点
-
新建一个结点,
ListNode* newNode = new ListNode(val);
,cur = newNode;
-
newHead = cur->next;
,cur->next = newHead;
注意这个顺序,

- 最后
size++
在尾部插入结点

-
新建一个结点,
ListNode* newNode = new ListNode(0);
这里默认指向的下一个就是NULL,cur = dummyHead;
-
循环如下
while (cur->next != NULL) {
cur = cur->next;
}
cur->next = newNode;
- 最后
size++
在第n个结点前插入结点

-
首先和第一个不一样的地方在
cur = dummyHead
-
新开辟一个结点
-
循环如下,关于这里的条件判断是否正确,k哥说之要带入一个极端的情况就ok了,也就是代入
n=0
试试。是没问题的
while (n--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
- 最后
size++
删除第n个结点

-
同样
cur = dummyHead
-
新开辟一个结点
-
循环如下
while (n--) {
cur = cur->next;
}
cur->next = cur->next->next;
- 最后
size--
LeetCode测试
有几点需要注意:
-
首先是最下面需要写
private
,用来定义dummyHead指针和size大小, -
在中间函数书写中,题目要求“将一个值为 val 的节点追加到...”,在书写时只需要
LinkNode(val)
就可以了 -
最后一个问题,就是指定索引的函数(插入或者删除)的index索引可以等于链表的长度。
这个报错找了半天,第33个用例一直卡住。😭😭最后找到啦😄问题出在addAtIndex()函数中,题目描述是这样的
如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将不会插入到链表中。
在之前的get()函数,在index索引的合法性判断时,因为我们索引是从0开始的,是不允许等于size
的,所以只能写成if (index > size - 1)
或者if (index >= size)
。但是这样就是一旦出现index为size
的情况就会判非法。所以要给条件判断修改if (index > size)
,
同理,deleteAtIndex()函数也要修改成if (index > size)
完整代码如下
点击查看代码
class MyLinkedList {
public:
struct LinkNode {
int val;
LinkNode* next;
LinkNode(int val):val(val), next(NULL){}
};
MyLinkedList() {
dummyHead = new LinkNode(0);
size = 0;
}
int get(int index) {
if (index > size - 1 || index < 0) {
return -1;
}
LinkNode* cur = dummyHead->next;
while (index--) {
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
LinkNode* newNode = new LinkNode(val);
LinkNode* cur = dummyHead;
newNode->next = cur->next;
cur->next = newNode;
// newNode->next = dummyHead->next;
// dummyHead->next = newNode;
size++;
}
void addAtTail(int val) {
LinkNode* newNode = new LinkNode(val);
LinkNode* cur = dummyHead;
while (cur->next != NULL) {
cur = cur->next;
}
cur->next = newNode;
size++;
}
void addAtIndex(int index, int val) {
if (index > size) {
return;
}
LinkNode* newNode = new LinkNode(val);
LinkNode* cur = dummyHead;
while (index--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
size++;;
}
void deleteAtIndex(int index) {
if (index >= size - 1 || index < 0) return;
LinkNode* cur = dummyHead;
while (index--) {
cur = cur->next;
}
LinkNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
size--;
}
private:
int size;
LinkNode* dummyHead;
};
LeetCode206
题目描述:力扣206
文档讲解:代码随想录(programmercarl)206.反转链表
视频讲解:《代码随想录》算法视频公开课:帮你拿下反转链表 | LeetCode:206.反转链表
代码随想录视频内容简记

双指针写法
感觉和数组中的双指针的一般做法还是有区别,不像之前的977.有序数组的平方
是双指针的两头像中中间靠拢,也不像27.移除元素
两个指针向右边走,也不像209.滑动窗口
那样设计两个指针来控制窗口的大小,这里的双指针是pre和cur一前一后紧紧跟随。
思想是一样的,时间复杂度还是,但是具体的作法还是有区别。
梳理
-
定义
pre
指针 -
定义
temp
指针 -
循环遍历
大致代码内容
-
pre = NULL
,即反转之后最后的尾结点指向 -
之所以要定义temp指针,是因为在上图第一步完成后,
cur->next
指针发生了变化,所以要提前定义temp = cur->next
-
while (cur != NULL)
,这里不是while (cur->next != NULL)
,注意是当cur指向NULL才停止循环的,cur->next = NULL
时还要执行一次 -
注意最后
return pre
。
递归写法
k哥说递归的写法和双指针的写法其实是一一对应的,首先要熟练掌握双指针的写法
递归的写法最后的时间复杂度也是,其实看每个元素被操作的次数可以发现,应该是每个元素的指向发生了一次变换,所以操作次数为n
,所以时间复杂度为
梳理
-
定义一个reverse()函数,接收两个参数,分别是
cur
和pre
-
递归的终止条件
-
递归中的调用,一定要注意赋值的含义
-
主函数中的调用
大致代码内容
-
reverse(ListNode* cur, ListNode* pre),这里的cur
-
if (cur == NULL) return pre
表示递归终止,返回反转后的pre头结点 -
递归函数reverse()中调用,首先应参照双指针的写法,给
pre = cur
,cur = temp
应该使用reverse(temp, cur)
,注意这里的解释,是把temp赋给上面的形参变量cur,把cur赋给上面的形参变量pre -
主函数中就直接写
reverse(cur, pre)
就行
LeetCode测试
双指针写法
点击查看代码
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL;
ListNode* cur = head;
ListNode* temp;
while (cur != NULL) {
// 反转
temp = cur->next;
cur->next = pre;
// 后移
pre = cur;
cur = temp;
}
return pre;
}
};
递归写法
注意实际上递归的作用就是对双指针指针后移写法的等价替代,所以
cur = tmp;
pre = cur;
这个要省去换成递归
点击查看代码
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head;
ListNode* pre = NULL;
return reverse(cur, pre);
}
ListNode* reverse(ListNode* cur, ListNode* pre) {
ListNode* tmp;
if (cur == NULL) return pre;
// 反转
tmp = cur->next;
cur->next = pre;
//cur = tmp;
//pre = cur;
// 递归
return reverse(tmp, cur);
}
};
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端