代码随想录训练营Day3 | 链表理论基础 | 203.移除链表元素 | 707.设计链表 | 206.反转链表

news2024/11/19 5:35:11

今天任务:学习链表理论基础                                     
                        链表的类型                                          
                        链表的存储方式                                   
                        链表的定义                                          
                        链表的操作                                          
                        性能分析                                             

学习文档:代码随想录 (programmercarl.com)

链表的类型单链表、双链表、循环链表(区别就在于其结构不同)

 链表是一种常用的数据结构,通过指针串联在一起,相对于数组有以下几方面优点:

  1. 动态大小:链表的大小是动态的,可以在运行时根据需要进行扩展或缩减。而数组的大小在声明时就固定了,不能动态改变。

  2. 内存利用率:链表不需要像数组那样预先分配一块连续的内存空间,因此可以更有效地利用内存,尤其是在内存碎片较多的情况下。

  3. 插入和删除操作搞笑:在链表中,插入和删除节点通常只需要改变指针,而不需要移动其他元素。这使得链表在插入和删除操作上比数组更高效,因为数组需要移动插入点或删除点之后的所有元素。

  4. 不需要初始化大小:在创建链表时,不需要指定链表的大小,可以根据需要逐步构建链表。

  5. 空间分配:链表的节点可以在需要时单独分配,这意味着即使链表很大,也不需要一次性分配大块内存,从而减少了内存的浪费。

  6. 灵活的数据结构:链表可以很容易地构建成其他复杂的数据结构,如双向链表、循环链表
    等,这些结构可以支持更复杂的操作。

链表也有其缺点,比如访问元素时需要从头开始遍历,导致访问时间较长;指针的额外存储空间可能会增加内存的开销;以及由于指针的存在,可能会导致程序的复杂性增加。 

链表的存储方式

数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。链表是通过指针域的指针链接在内存中各个节点。所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

链表的定义

单链表的定义 (特别注意,在面试中可能需要自己定义链表

// 单链表
struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

 链表的基本操作:删除、添加

删除节点

删除D节点,如图所示:

只要将C节点的next指针 指向E节点就可以了。注意此时D节点依旧留在内存中,只不过是没有在这个链表中而已,在使用C++最好手动释放这个D节点,释放这块内除。

添加节点

可以看出链表的增添和删除都是O(1)操作,也不会影响到其他节点。但是要注意,要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)

性能分析

Leetcode: 203.移除链表元素

题目描述:

 给你一个链表的头节点 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
输出:[]

解题思路:

这题就是简单的删除链表元素,但是要注意区分两种删除方式
1.删除头节点
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* removeElements(ListNode* head, int val) {
        // 这里需要时while 因为删除有可能需要一直删 不止一个val
        // 如果val是头节点 直接将head = head->next;即可
        while(head!=NULL && head->val == val) {
            ListNode* tmp = head;
            head = head->next;
            delete tmp;
        }
        ListNode* cur = head;
        // 如果不是头节点,需要cur->next = cur->next->next; 这样就删除了cur->next这个节点
        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;
    }
};

 使用虚拟头节点:

使用一个虚拟头节点,可以统一逻辑来删除链表节点 

/**
 * 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(0);
        // 将虚拟头节点设置为这个链表的头节点
        dummyHead->next = head;
        // 从虚拟头节点开始遍历
        ListNode*cur = dummyHead;
        // 统一删除节点逻辑 都是删除非头节点
        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;
            }
        }
        // 重新设置头节点
        head = dummyHead->next;
        delete dummyHead;
        return head;
    }
};

Leetcode: 707.设计链表

 题目描述

你可以选择使用单链表或者双链表,设计并实现自己的链表。单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。

实现 MyLinkedList 类:

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

 解题思路

这道题目设计链表的五个接口:

  • 获取链表第index个节点的数值
  • 在链表的最前面插入一个节点
  • 在链表的最后面插入一个节点
  • 在链表第index个节点前面插入一个节点
  • 删除链表的第index个节点

可以说这五个接口,已经覆盖了链表的常见操作,是练习链表操作非常好的一道题目

可以继续使用上面的操作,使用虚拟头节点来操作

class MyLinkedList {
public:
    // 定义链表节点的结构体
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val) : val(val), next(nullptr){}
    };
    // 初始化链表 这里定义的头节点是一个虚拟头节点 而不是真正的链表头节点
    MyLinkedList() {
        _dummyhead = new LinkedNode(0);
        // 一个整型变量,用于存储链表中实际节点的数量
        _size = 0;
    }
    // 获取链表第index个节点的数值
    int get(int index) {
        // 如果索引无效(即超出链表范围),返回 -1
        if(index > (_size - 1) || index < 0) {
            return -1;
        }
        // 从虚拟头节点的下一个节点开始遍历,直到到达指定索引的节点,然后返回该节点的值
        LinkedNode* cur = _dummyhead->next;
        while(index--) {
            cur = cur->next;
        }
        return cur->val;
    }
    // 在链表最前面插入一个节点 ,插入完成后,新插入的节点为链表新的头节点
    void addAtHead(int val) {
        LinkedNode* newnode = new LinkedNode(val);
        // 注意这里的顺序不能改变 统一插入的赋值顺序 先将新头节点插入在head之前 然后将虚拟头节点依旧放在最前面
        newnode->next = _dummyhead->next;
        _dummyhead->next = newnode;
        _size++;
    }
    // 在链表最末尾插入节点
    void addAtTail(int val) {
        LinkedNode* newnode = new LinkedNode(val);
        LinkedNode* cur = _dummyhead;
        // 先将cur指向最后一个节点 判断条件cur->next != NULL
        while(cur->next != NULL) {
            cur = cur->next;
        }
        cur->next = newnode;
        _size++;
    }
    // 在第index个节点之前插入一个新节点 使用虚拟头节点就可以方便处理index为0 插入头节点的情况
    void addAtIndex(int index, int val) {
        if(index > _size) {
            return;
        }
        LinkedNode* newnode = new LinkedNode(val);
        LinkedNode* cur = _dummyhead;
        while(index--) {
            cur = cur->next;
        }
        newnode->next = cur->next;
        cur->next = newnode;
        _size++;
    }
    
    //删除第index个节点
    void deleteAtIndex(int index) {
        //当 index 等于链表长度时,cur->next 将为 nullptr,因此不能访问 cur->next->next
         if(index >= _size || index < 0) {
            return;
        }
        LinkedNode* cur = _dummyhead;
        // 注意index是从0开始的 这样刚好指向index前一个节点
        while(index--) {
            cur = cur->next;
        }
        LinkedNode* tmp = cur->next;
        // 删除index节点
        cur->next = cur->next->next;
        delete tmp;
        _size--;
    }
    private:
        int _size;
        LinkedNode* _dummyhead;
};

 总结:要用意识去使用虚拟头节点,对于一些边界条件判断不到位!

Leetcode: 206.反转链表

题目描述

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

示例 1:

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

 解题思路

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* reverseList(ListNode* head) {
        ListNode* former = NULL;
        ListNode* mid    = head;
        ListNode* latter = NULL;

        while(mid != NULL) {
        // 保存mid的下一个节点,因为接下来要改变mid->next的指向了
            latter = mid->next;
            mid->next = former;
            former = mid;
            mid = latter;
        }

        // 注意最后一次while循环 将latter赋给了mid 所以former是反转链表后的头节点
        return former;
    }
};

2.递归法

递归法相对抽象一些,但是其实和双指针法是一样的逻辑,同样是当cur为空的时候循环结束,不断将cur指向pre的过程。

关键是初始化的地方, 可以看到双指针法中初始化 cur = head,pre = NULL,在递归法中可以从如下代码看出初始化的逻辑也是一样的,只不过写法变了。

具体可以看代码(已经详细注释),双指针法写出来之后,理解如下递归写法就不难了,代码逻辑都是一样的。

class Solution {
public:
    ListNode* reverse(ListNode* pre,ListNode* cur){
        if(cur == NULL) return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        // 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
        // pre = cur;
        // cur = temp;
        return reverse(cur,temp);
    }
    ListNode* reverseList(ListNode* head) {
        // 和双指针法初始化是一样的逻辑
        // ListNode* cur = head;
        // ListNode* pre = NULL;
        return reverse(NULL, head);
    }

};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot+Vue+MySQL的招聘管理系统

系统展示 用户前台界面 管理员后台界面 企业后台界面 系统背景 在当今数字化转型的大潮中&#xff0c;企业对于高效、智能化的人力资源管理系统的需求日益增长。招聘作为人力资源管理的首要环节&#xff0c;其效率与效果直接影响到企业的人才储备与竞争力。因此&#xff0c;构建…

linux 操作系统下crontab命令及使用案例介绍

linux 操作系统下crontab命令及使用案例介绍 Linux 操作系统下的 crontab 命令用于设置周期性执行的任务 crontab 命令概述 基本语法 bash crontab [-u user] file crontab [-u user] [-l | -r | -e] [-i] [-s] 主要功能 创建、编辑和管理用户的计划任务&#xff08;cron…

基于中心点的目标检测方法CenterNet—CVPR2019

Anchor Free目标检测算法—CenterNet Objects as Points论文解析 Anchor Free和Anchor Base方法的区别在于是否在检测的过程中生成大量的先验框。CenterNet直接预测物体的中心点的位置坐标。 CenterNet本质上类似于一种关键点的识别。识别的是物体的中心点位置。 有了中心点之…

【工具】前端JavaScript代码在线执行器 方便通过网页 手机测试js代码

【工具】前端JavaScript代码在线执行器 方便通过网页 手机测试js代码 自动补全js代码格式化代码色彩打印日志清空日志待补充 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport"…

基于SpringBoot+Vue+MySQL的热门网络游戏推荐系统

系统展示 用户前台界面 管理员后台界面 系统背景 基于SpringBootVueMySQL的热门网络游戏推荐系统&#xff0c;其背景主要源于当前网络游戏市场的蓬勃发展与用户需求的日益多样化。随着互联网的普及和技术的不断进步&#xff0c;网络游戏已成为人们休闲娱乐的重要方式之一。面对…

JAVA开源项目 校园管理系统 计算机毕业设计

本文项目编号 T 026 &#xff0c;文末自助获取源码 \color{red}{T026&#xff0c;文末自助获取源码} T026&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 管…

网络安全-intigriti-0422-XSS-Challenge Write-up

目录 一、环境 二、解题 2.1看源码 一、环境 Intigriti April Challenge 二、解题 要求&#xff1a;弹出域名就算成功 2.1看源码 我们看到marge方法&#xff0c;肯定是原型链污染题目 接的是传参&#xff0c;我们可控的点在于qs.config和qs.settings&#xff0c;这两个可…

逆向工程 反编译 C# net core

索引器访问 在您的代码中&#xff0c;您试图使用 configurationRoot.get_Item("AgileConfig:appId") 来访问配置项&#xff0c;但这里存在几个问题&#xff1a; 错误的访问方法&#xff1a;在 .NET 的 IConfigurationRoot 接口中&#xff0c;没有直接名为 get_Item 的…

python fastapi 打包exe

创建虚拟环境 python -m venv 国内依赖仓库 # 换源 pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple pip config set install.trusted-host mirrors.aliyun.com 安装nuitka pip install nuitka 生成exe nuitka --mingw64 --show-progress --s…

[000-01-008].第08节:Sentinel 环境搭建

1.Sentinel的构成&#xff1a; 核心库-后台默认的端口是8719控制台-前台默认的是8080端口 2.2.搭建Sentinel环境&#xff1a; a.下载Sentinel&#xff1a; 1.sentinel官方提供了UI控制台&#xff0c;方便我们对系统做限流设置。可以在GitHub下载 b.下载后运行Sentinel&#…

自动驾驶系列—掌握速度,驾驭安全,限速信息提醒功能(SLIF)介绍

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

心觉:收钱就像喝水一样简单,是如何做到的?

Hi&#xff0c;我是心觉&#xff0c;与你一起玩转潜意识、脑波音乐和吸引力法则&#xff0c;轻松掌控自己的人生&#xff01; 挑战每日一省写作168/1000天 真的存在赚钱跟喝水一样简单的事情 这两天亲身体验过 某位做知识付费的大佬&#xff0c;昨天写一篇文章就哗哗哗的开…

导弹追踪问题:蒙特卡罗模拟+matlab代码

问题描述 蒙特卡罗模拟思想 采用微元法以直代曲的思想&#xff0c;假设一个个小时间段内&#xff0c;B船先走完一段直线距离后&#xff0c;导弹朝着两者连线方向走直线&#xff0c;这样若干条直线便近似拟合导弹轨迹。代码中判断碰撞的依据是A、B之间的距离小于某个阈值&#x…

JSON 数据 Excel 行转列

有如下JSON数据 [{id:1,name:小明,score:90}, {id:2,name:小李,score:89}, {id:3,name:小王,score:77}, {id:4,name:小刘,score:56}] 粘贴到 Excel 选中列-->数据tab-->分列 下一步 分隔符号-->其他【,】-->完成 CtrlF 替换-->全部替换 掉不要的字符为空 得…

代码随想录Day 43|leetcode题目:300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组

提示&#xff1a;DDU&#xff0c;供自己复习使用。欢迎大家前来讨论~ 文章目录 动态规划Part10题目一&#xff1a;300.最长递增子序列解题思路&#xff1a; 题目二&#xff1a;674. 最长连续递增序列解题思路&#xff1a; 题目三&#xff1a; 718. 最长重复子数组解题思路滚动…

Radiance Field Learners As UAV First-Person Viewers 精读

1. 多尺度相机空间估计模块&#xff1a; 关键帧选择器&#xff1a;自动选择最具代表性的帧进行渲染&#xff0c;减少计算量&#xff0c;提高渲染效率。无人机轨迹预测&#xff1a;通过历史轨迹预测无人机的未来位置&#xff0c;确保实时视角调整&#xff0c;提高无人机导航的准…

云渲染与AI渲染分别是什么?两者的优势对比

云渲染和AI渲染是两种先进的渲染技术&#xff0c;它们各自具有独特的优势和应用场景。下面针对两种情况来简单说明下。 1、云渲染&#xff1a; - 定义&#xff1a;云渲染是一种利用远程服务器(云端)来处理和生成渲染效果的技术。它允许用户将计算密集型的任务转移到云端&#…

[论文笔记] CSFCN

摘要 上下文建模或多级特征融合方法已被证明可以有效提高语义分割性能。 然而&#xff0c;它们并不是专门处理像素上下文不匹配和空间特征不对齐的问题&#xff0c;并且高计算复杂度阻碍了它们在实时场景中的广泛应用。 在这项工作中&#xff0c;我们提出了一种轻量级的上下文…

8.第二阶段x86游戏实战2-实现瞬移

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

【C++11 —— 包装器】

C11 —— 包装器 包装器function包装器function包装器介绍function包装器统一类型function包装器的意义 bind包装器bind包装器介绍bind包装器绑定固定参数bind包装器调整传参顺序bind包装器的意义 包装器 function包装器 function包装器介绍 function包装器 也叫作适配器。C…