代码随想论算法训练营第3天 | 链表理论基础,203.移除链表元素,707.设计链表,206.反转链表

news/2025/3/9 10:53:54/文章来源:https://www.cnblogs.com/xuchiblog/p/18664186

一、刷题部分

1.1 链表理论基础

  • 原文链接:代码随想录
  • 题目链接:🈚️

链表是由一个个节点串联而成的,节点包含数据域和指针域,数据域用来存放数据,而指针域实现了节点之间的串联。

链表中有单链表、双链表、循环链表:

链表的物理空间是不连续的,通过指针存储下一节点的物理地址。

链表的简单定义需要能够熟练会写:

// 单链表节点
struct ListNode {int val;ListNode* next;ListNode(int x) : val(x), next(NULL) {}ListNode(int x, ListNode* p) : val(x), next(p) {}
}

有时候只需节点定义就可以初始化链表了,但也可以完善一下把链表类封装好:

// 单链表
class LinkList {
public:struct ListNode {int val;ListNode* next;ListNode(int x) : val(x), next(NULL) {}ListNode(int x, ListNode* p) : val(x), next(p) {}}ListNode(){}
private:ListNode *head = new ListNode();
}

链表的删除,插入等操作都是对节点的,需要注意处理指针的关系,很多时候需要额外定义临时指针来辅助暂存一些地址。

链表可以低成本地增删节点,但是链表的查询成本就比数组高,这个特点决定链表适合于查询操作较少而增删操作较多的情景。

1.2 203.移除链表元素

  • 原文链接:代码随想录
  • 题目链接:203. 移除链表元素 - 力扣(LeetCode)

1.2.1 题目描述

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

提示:

  • 列表中的节点数目在范围 [0, 104]
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

1.2.2 初见想法

删除链表 val 需要先把开头的 val 节点全部删掉才可以开始后面的工作。代码逻辑如下:

/*** 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 *p = nullptr;ListNode *temp = nullptr;//将链表开头所有val结点删除while(head != nullptr && head -> val == val){temp = head;head = head -> next;delete temp;}//开头一删发现全空了,直接返回if(head == nullptr) return head;p = head;while(p -> next != nullptr){if(p -> next -> val == val){temp = p -> next;p -> next = p -> next -> next;delete temp;continue;}p = p -> next;}return head;}
};

1.2.3 看录后想法

如果题目定义的链表结构使用实头结点,那么可以自行定义一个虚拟头结点 DummyHead 来统一操作方式。

/*** 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* DummyHead = new ListNode(-1, head);ListNode* p = DummyHead;while(p -> next != nullptr){if(p -> next -> val == val){ListNode* temp = p -> next;p-> next = p-> next -> next;delete temp;continue;}p = p-> next;}head = DummyHead -> next;delete DummyHead;return head;}
};

1.2.4 遇到的困难

没有想到虚拟头节点的方法,所以直接写的时候总是感觉不太清晰,操作很繁杂。

1.3 707.设计链表

  • 原文链接:代码随想录
  • 题目链接:707. 设计链表 - 力扣(LeetCode)

1.3.1 题目描述

你可以选择使用单链表或者双链表,设计并实现自己的链表。

单链表中的节点应该具备两个属性:valnextval 是当前节点的值,next 是指向下一个节点的指针/引用。

如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。

实现 MyLinkedList 类:

  • MyLinkedList() 初始化 MyLinkedList 对象。
  • int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1
  • `void addAtHe
  • ad(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
  • void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
  • void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
  • void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

示例:

输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2);    // 链表变为 1->2->3
myLinkedList.get(1);              // 返回 2
myLinkedList.deleteAtIndex(1);    // 现在,链表变为 1->3
myLinkedList.get(1);              // 返回 3

提示:

  • 0 <= index, val <= 1000
  • 请不要使用内置的 LinkedList 库。
  • 调用 getaddAtHeadaddAtTailaddAtIndexdeleteAtIndex 的次数不超过 2000

1.3.2 初见想法

就是一些功能函数的实现。写一下看看吧:

class MyLinkedList {
public://定义链表结构体struct MyLinkedNode{int val;MyLinkedNode* next;MyLinkedNode() : val(-1), next(nullptr){}MyLinkedNode(int x) : val(x), next(nullptr){}MyLinkedNode(int x, MyLinkedNode* next) : val(x), next(next){}};MyLinkedList(){_DummyHead = new MyLinkedNode();_size = 0;}int get(int index) {//cout<<"get("<<index<<")---";if(index < 0 || index >= _size) return -1;MyLinkedNode* cur = _DummyHead -> next;for(int i = 0; i < index; i++){cur = cur -> next;}return cur -> val;}void addAtHead(int val) {//cout<<"addAtHead("<< val << ")---\n";MyLinkedNode* temp = new MyLinkedNode(val, _DummyHead -> next);_DummyHead->next = temp;_size++;//cout<<"现在的size是"<<_size<<endl;//printLink();}void addAtTail(int val) {//cout<<"addAtTail("<<val<<")---\n";MyLinkedNode* cur = _DummyHead;for(int i = 0; i < _size; i++){cur = cur -> next;}cur -> next = new MyLinkedNode(val);_size++;//cout<<"现在的size是"<<_size<<endl;//printLink();}void addAtIndex(int index, int val) {//cout<<"addAtIndex("<<index<<", "<<val<<")---\n";//先处理特殊情况if(index > _size || index < 0) return;else if(index == _size){addAtTail(val);return;}else if(index == 0){addAtHead(val);return;}//正式开始MyLinkedNode* cur = _DummyHead;for(int i = 0; i < index; i++){cur = cur -> next;}MyLinkedNode* temp = new MyLinkedNode(val, cur -> next);cur -> next = temp;_size++;//cout<<"现在的size是"<<_size<<endl;//printLink();}void deleteAtIndex(int index) {//cout<<"deleteAtIndex("<<index<<")---\n";if(index < 0 || index >= _size) return;MyLinkedNode* cur = _DummyHead;for(int i = 0; i < index; i++)cur = cur -> next;MyLinkedNode* temp = cur -> next;cur -> next = cur -> next -> next;delete temp;_size--;//cout<<"现在的size是"<<_size<<endl;//printLink();}void printLink(){cout<<"现在的链表为:[";MyLinkedNode* cur = _DummyHead -> next;for(int i = 0; i < _size; i++){cout<< cur->val << ", ";}cout<<"]\n";}
private:MyLinkedNode* _DummyHead;int _size;
};/*** 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);*/

1.3.3 看录后想法

这题就是基本写法的实现。

1.3.4 遇到的困难

有一些小错误不容易发现,老是编译失败,后来发现是一个条件判断写错了。花挺久时间在这上面的。

1.4 题目名称

  • 原文链接:代码随想录
  • 题目链接:206. 反转链表 - 力扣(LeetCode)

1.4.1 题目描述

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目范围是 [0, 5000]
  • -5000 <= Node.val <= 5000

进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?

1.4.2 初见想法

是一道指针的基本操作问题,通过修改指针从而完成这道题目。用 3 个指针pre,cur,nxt来指向3个节点,然后每次修改 cur 的 next 指针指向 pre ,从而完成这道题目。感觉最困难的点是在开始和结束的过程需要注意一些细节。写写看吧:

/*** 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* cur = head;ListNode* prv = nullptr;ListNode* nxt = nullptr;while(cur){nxt = cur -> next;cur -> next = prv;prv = cur;cur = nxt;}head = prv;return head;}
};

1.4.3 看录后想法

所谓的双指针法与我的想法有一定的相似之处的,不过视频讲解里使用2个指针先分析过程,然后第三个临时指针在写具体过程的时候临时加上去,我认为这个思路是比较流畅的,比我的想法要好一些。

再用双指针的思路写一遍:

/*** 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 *cur = head;ListNode *pre = nullptr;while (cur != nullptr) {//设立临时变量保存下一个节点的地址ListNode *nxt = cur -> next;//指针更改cur -> next = pre;//向后移动pre = cur;cur = nxt;}return pre;}
};

发现这个看起来和我之前写的那一版极其相似,但是我觉得我的思路会很清晰,因此这个再写一遍的代码还是挺重要的。

接下来是递归的方法:

然而本题的递归方式我是看起来极其困难的,视频讲解用的是将原来的双指针法一一对应过去,但是这样子去写一个递归还有什么意义吗?我觉得需要不用双指针法而能够写出递归这样才是真正把递归学明白了。

现在我需要找找递归地资料好好巩固一下递归思想来看看能不能解决我现在的困惑。

感觉不行,这题上来一个递归实在有点奇怪,可能是我目前确实水平达不到,不急,就先按照录里的思想,对照着双指针法来一遍递归吧。

//递归写法
class Solution {
public:ListNode* reverseList(ListNode* head) {return reverse(nullptr, head);}ListNode* reverse(ListNode* pre, ListNode* cur) {//递归第一步:先写好出口条件if (cur == nullptr) {return pre;}//接下来仿照双指针法,先定义临时变量存下一节点ListNode *nxt = cur -> next;//指针转向cur -> next = pre;//继续往后递归return reverse(cur, nxt);}
};

1.4.4 遇到的困难

递归的思路总是觉得奇怪,可能是这块我掌握的不好。我认为这个问题不是目前的我可以解决的,而是自己水平提高后自然就掌握的能力,因此这里就不多花费功夫了。

二、总结与回顾

今天搞了一下链表的几道题目,总体来看还是比较简单的。希望可以后面再接再厉。

学习时长:4h

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/867206.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ABAP配置:OY01 定义国家/地区

配置:OY01 定义国家/地区 事务代码:OY01 配置路径 SPRO-ABAP平台-常规设置-设置国家-定义国家/地区 配置路径截图配置描述 国家是SAP里面一个非常重要的概念,SAP国家概念涉及公司代码、工厂、主数据、跨国银行交易,系统默认自带ISO相关的国家编码,在S/4中,一些配置转移到…

关于GTM,这些评价指标你都知道吗?

目标跟踪指标是企业实现持续增长和盈利的重要工具。通过定期监控和分析这些指标,企业可以及时发现潜在问题并采取相应的改进措施,以保持其竞争力并实现业务目标。因此,企业应该重视这些指标的应用,并不断优化其监控和分析流程,以确保其业务运营的顺利进行。Goal Tracking …

ODX诊断数据库转换工具 - DDC

INTEWORK-DDC (Diagnostic Database Convertor) 是将诊断调查问卷转换为标准ODX(2.2.0)数据库的工具。ODX是格式标准化的诊断数据库文件,我们在诊断不同的车或者不同的ECU时,只需要加载适配这个车型或ECU的ODX文件即可,而无需对诊断仪做任何改变。ODX统一了诊断文件的格式,…

异地多活架构进阶:如何解决写后立即读场景问题?【转】

在《醍醐灌顶!异地多活架构设计看这篇就够了》一文中,基于容灾需要,讨论了数据写入的架构模型。数据读取方面,重点在于解决读取请求的负载分担、路由选择的问题,对于容灾架构的选择影响不大。不过,其中的“写后立即读”场景,是个一致性范畴的问题,即写入的数据和写入后…

JAVA之面向对象

1、设计对象并使用类和对象 类(设计图):是对象共同特征的描述; 对象:是真实存在的具体实例; 在java中,必须先设计类,才能获得对象。 如何得到类的对象:类名 对象名 = new 类名(); 如何使用对象: 访问属性:对象名.成员变量 访问行为:对象名.方法…

免费手动打Windows Server补丁

免费手动打Windows Server 2008 R2补丁https://catalog.update.microsoft.com/search.aspx?q=kb4474419然后到windows上双击运行即可本文来自博客园,作者:六月OvO,转载请注明原文链接:https://www.cnblogs.com/chenlifan/p/18664077

pwn1_sctf_2016 1

打开ida反汇编看一下,是c++,无所谓,复制问一下ai先让我们输入s的数据,读取长度限制在32字节。然后replace函数会将s里面的 I 替换成 you 。最后输出s。 分析一下,s距离ebp为0x3C(60字节),且我们最多只能输入32字节的,但经过replace函数,一个字节的‘I’会被替换成三个…

UDS-ECU程序刷写

UDS(unified diagnostic services)统一诊断服务主要是针对汽车上对ECU进行诊断服务规范,下图是UDS在OSI分层中的具体规范,基于UDS的刷写应用逻辑体现在应用层的ISO14229规范。一、功能介绍 UDS(unified diagnostic services)统一诊断服务主要是针对汽车上对ECU进行诊断服…

主体分割技术,提升图像信息提取能力

在智能设备普及和AI技术进步的推动下,用户对线上互动的质量、个性化以及沉浸式体验的追求日益增强。例如,对于热衷于图片编辑或视频制作的用户来说,他们需要一种快速而简便的方法来将特定主体从背景中分离出来。 HarmonyOS SDK 基础视觉服务(Core Vision Kit)提供主体分割…

qt 实现窗口置顶,qtdesigner创建的widget窗口集成程序里的用法

参考 https://blog.csdn.net/Larry_Yanan/article/details/123518788 .ui文件如下新建的ui文件,编译一下就会生成对应的 ui_xxx.h 文件,文件内就有对应的 namespace Ui 声明的变量,这个变量要在mainwindow.h中声明,然后在mainwindow.cpp中new出来,具体使用如下 mainwindow…

如何在市场推广活动中实现精准的任务分配?5个项目管理技巧

一、引言 随着市场竞争的加剧和消费者需求的多样化,企业对市场推广活动的要求越来越高。市场推广活动不仅需要创意和精准的目标定位,还需要高效的执行和完善的管理。在这种背景下,如何通过有效的活动管理来提升推广活动的执行力,已成为市场团队面临的一个巨大挑战。 市场推…

Android编译 - 证书介绍

前言全局说明一、说明 1.1 环境: Android1.2 简介 在Android系统中,每个APK文件必须有一个有效的数字证书来证明其来源和完整性。当需要修改APK后再次发布时,原有的签名将不再有效,因此需要重新签名。二、证书工具 2.1 路径: android/build/tools/releasetools/sign_target…