DAY03|203.移除链表元素、707.设计链表、206.反转链表

203.移除链表元素、707.设计链表、206.反转链表

  • LeetCode 203.移除链表元素
  • LeetCode 707.设计链表
  • LeetCode 206.反转链表
    • 双指针法
    • 递归法

LeetCode 203.移除链表元素

注意,在dummy上操作,返回也返回dummy->next
如果头铁想返回head,那样会发现head没有变化
因为实际上dummy->next和head并不是同一块地址空间的内容
函数里的操作改变的都是新的dummy节点上进行的
原始链表的连接关系没有改变

    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* pre= dummy;
        ListNode* cur = dummy->next;
        while(cur != NULL){
            if(cur->val == val){
                pre->next = cur->next;
                cur = pre->next;
            }else{
                //这个else是后面补的,原先没写else出了一堆问题,还又增加了if分支
                cur=cur->next;
                pre=pre->next;
            }
        }
        return dummy->next;
    }

因为一个else卡了半天查呀查呀查
是我没有想到的
如果不写else问题很大,那样这里要加一个判断,再判断是否cur为空
这还不够,不写else如果上面if内部执行了,就不能再往下推进pre和cur,这也是为啥要写else
不是很难想的逻辑,但是第一遍写代码甚至好几遍检查的时候都没有查出来这个点,思维还是混乱
这道题21年也写过通过了
ok接下来看看卡哥的

链表操作中,可以使用原链表来直接进行删除操作,也可以设置一个虚拟头结点再进行删除操作,接下来看一看哪种方式更方便。
21年的代码相当于前者,今天的代码相当于后者

卡哥说:还要说明一下,就算使用C++来做leetcode,如果移除一个节点之后,没有手动在内存中删除这个节点,leetcode依然也是可以通过的,只不过,内存使用的空间大一些而已,但建议依然要养成手动清理内存的习惯。
这个我没有写

那么因为单链表的特殊性,只能指向下一个节点,刚刚删除的是链表的中第二个,和第四个节点,那么如果删除的是头结点又该怎么办呢?
这里就涉及如下链表操作的两种方式:
(1)直接使用原来的链表来进行删除操作。

 ListNode* removeElements(ListNode* head, int val) {
        // 删除头结点
        while (head != NULL && head->val == val) { // 注意这里不是if
            ListNode* tmp = head;
            head = head->next;
            delete tmp;}
        // 删除非头结点
        ListNode* cur = head;
        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;
    }

(2)设置一个虚拟头结点在进行删除操作。

ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
        dummyHead->next = head; // 将虚拟头结点指向head,这样方便后面做删除操作
        ListNode* cur = dummyHead;
        while (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;
    }

附:和鸡皮题的对线记录

鸡皮题:实际上,dummy->next 和 head 在这段代码中并不是同一块地址空间的内容。在函数中的操作并没有改变原始链表中的节点的连接关系,而是在新的dummy节点上进行操作,因此返回的结果应该为dummy->next 而不是 head。
当你在函数返回 dummy->next 时,实际上是返回了经过修改后的链表头。原始的 head 节点并没有发生改变,因为你在操作链表时只是在 dummy 节点上进行的连接修改。
如果你想改变原始链表的头节点,可以修改函数的返回类型为 ListNode* & 或者使用引用类型进行传参,并在函数中通过修改传入的引用参数来修改原链表头节点。
我:但没有引用类型,head传过去的也是一个实际的内存地址啊
(心里确实有这样的疑惑,因为和我的常识不符)
鸡皮题:在C++中,虽然我们传递指针或者引用参数时是传递的地址,但在函数内部进行的指针或引用的指向修改,并不影响原始指针或引用的指向。因此,在函数内部操作的数据只是在函数内部生效,不会影响原始数据的内容。所以,你在上述代码中对链表进行的操作并没有修改原始链表的连接关系,所以返回的是原始链表头节点的地址。
如果你想修改原始链表,可以通过引用传递一个指向链表头节点的指针或者引用,在函数内部通过修改传入的指针或引用来改变原始链表的内容。
我:地址指向的修改都不影响吗?
(常识是肯定是修改的啊,指针传参数,引用传参数,这是我的常识)
鸡皮题:地址指向的修改是影响的,但是在你的代码中,你只是通过改变pre->next指针的指向来实现删除节点的操作,并没有改变head指针本身的值。因此,虽然dummy->next和head初始时指向同一个节点,但是在函数执行过程中,只有dummy->next的指向发生了变化,而head指针本身并没有被修改。
如果你希望返回的是修改后的链表头节点,你可以在函数内部维护一个新的指针变量来记录新的头节点,然后在操作结束后返回这个新的头节点。这样就能返回修改后的链表了。

鸡皮题是这样把我说服的,确实应该是这个样子的,我倒腾的指针都没有去试图改变head本身
有这么一个模糊的抽象,但并不是很清晰,说明思维还是有些混乱

LeetCode 707.设计链表

这个难
读了题云了一会,直接看卡哥的吧

class MyLinkedList {
public:
    // 定义链表节点结构体
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val), next(nullptr){}
    };

    // 初始化链表
    MyLinkedList() {
        _dummyHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
        _size = 0;
    }

    // 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点
    int get(int index) {
        if (index > (_size - 1) || index < 0) {
            return -1;
        }
        LinkedNode* cur = _dummyHead->next;
        while(index--){ // 如果--index 就会陷入死循环
            cur = cur->next;
        }
        return cur->val;
    }

    // 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
    void addAtHead(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        newNode->next = _dummyHead->next;
        _dummyHead->next = newNode;
        _size++;
    }

    // 在链表最后面添加一个节点
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = newNode;
        _size++;
    }

    // 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果index大于链表的长度,则返回空
    // 如果index小于0,则在头部插入节点
    void addAtIndex(int index, int val) {

        if(index > _size) return;
        if(index < 0) index = 0;        
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(index--) {
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
        _size++;
    }

    // 删除第index个节点,如果index 大于等于链表的长度,直接return,注意index是从0开始的
    void deleteAtIndex(int index) {
        if (index >= _size || index < 0) {
            return;
        }
        LinkedNode* cur = _dummyHead;
        while(index--) {
            cur = cur ->next;
        }
        LinkedNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        //delete命令指示释放了tmp指针原本所指的那部分内存,
        //被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
        //如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
        //如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
        tmp=nullptr;
        _size--;
    }

    // 打印链表
    void printLinkedList() {
        LinkedNode* cur = _dummyHead;
        while (cur->next != nullptr) {
            cout << cur->next->val << " ";
            cur = cur->next;
        }
        cout << endl;
    }
private:
    int _size;
    LinkedNode* _dummyHead;

};

今天有点上强度
现在有点困,明天又直接4道题+链表总结
这个强度是否有点太高了
准备休息了,梦里继续想
无所谓,我会开摆

LeetCode 206.反转链表

这个感觉还好,没有上个那么脑袋疼
但也不是一遍过的
最开始没有else break;陷入死循环了

    ListNode* reverseList(ListNode* head) {
        ListNode* pre = NULL;
        ListNode* cur = head;
        while(cur){
            ListNode* tmp = NULL;
            if(cur->next) tmp = cur->next;
            cur->next = pre;
            if(tmp){
                pre = cur;
                cur = tmp;
            }else break; 
        }
        return cur;
    }

如果再定义一个新的链表,实现链表元素的反转,其实这是对内存空间的浪费。
其实只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表,如图所示:
反转链表
之前链表的头节点是元素1, 反转之后头结点就是元素5 ,这里并没有添加或者删除节点,仅仅是改变next指针的方向。

(1)首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
(2)然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
(3)为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
(4)接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。
(5)最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。

双指针法

ListNode* reverseList(ListNode* head) {
        ListNode* temp; // 保存cur的下一个节点
        ListNode* cur = head;
        ListNode* pre = NULL;
        while(cur) {
            temp = cur->next;  // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur->next = pre; // 翻转操作
            // 更新pre 和 cur指针
            pre = cur;
            cur = temp;
        }
        return pre;
    }

感觉相比之下我的代码有点瞎忙活
明明不用ifelse分支去判断的,我写了俩分支去判断
这里我的思路也不是很清晰

递归法

    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);
    }

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

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

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

我们可以发现,上面的递归写法和双指针法实质上都是从前往后翻转指针指向,其实还有另外一种与双指针法不同思路的递归写法:从后往前翻转指针指向。

 ListNode* reverseList(ListNode* head) {
        // 边缘条件判断
        if(head == NULL) return NULL;
        if (head->next == NULL) return head;
        
        // 递归调用,翻转第二个节点开始往后的链表
        ListNode *last = reverseList(head->next);
        // 翻转头节点与第二个节点的指向
        head->next->next = head;
        // 此时的 head 节点为尾节点,next 需要指向 NULL
        head->next = NULL;
        return last;
    }

递归法没有看,脑袋疼,困,睡觉了

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024高交会-2024深圳高新技术展-高新技术成果交易会

2024高交会-2024深圳高新技术展-2024高新技术成果展-中国高校技术交易会-第26届高交会-深圳高交会-深圳高科技展-深圳新科技展-深圳高新技术成果展 第二十六届中国国际高新技术成果交易会&#xff08;简称高交会&#xff09; 时间&#xff1a;2024年11月15日-19日 地址&#…

点击按钮(文字)调起elementUI大图预览

时隔一年&#xff0c;我又回来了 ~ 最近在做后台&#xff0c;遇到一个需求&#xff0c;就是点击“查看详情”按钮&#xff0c;调起elementUI的大图预览功能&#xff0c;预览多张图片&#xff0c;如下图&#xff1a; 首先想到的是使用element-ui的el-image组件&#xff0c;但它是…

NzN的数据结构--交换排序

篇接上文&#xff0c;今天要学习的就是交换排序&#xff0c;这么励志的日更博主&#xff0c;你怎么能不三连一下呢&#xff1f; 目录 一、基本思想 二、冒泡排序 三、快速排序 1. hoare版本 2. 挖坑法 3. 前后指针法 4. hoare版本优化 5. 非递归实现快速排序 一、基本…

模拟移动端美团案例(react版)

文章目录 目录 概述 项目搭建 1.启动项目&#xff08;mock服务前端服务&#xff09; 2.使用Redux ToolTik(RTK)编写store(异步action) 3.组件触发action并渲染数据 一、渲染列表 ​编辑 二、tab切换类交互 三、添加购物车 四、统计区域功能实现 五、购物车列表功能实现 六、控制…

【Java集合进阶】泛型的通配符和综合练习

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

加速度JUSDO | 电子元器件商城行业调研及运营方案

一、行业背景与竞品分析 随着电子元器件行业的快速发展&#xff0c;线上元器件商城已成为行业交易的重要渠道。目前市场上存在多个知名的元器件商城&#xff0c;如立创、云汉芯城、贸泽商城等&#xff0c;它们都提供了丰富的元器件产品和便捷的线上交易服务。 立创商城&#x…

蓝桥杯第十三届c++大学B组详解

目录 1.九进制转十进制 2.顺子日期 3.刷题统计 4.修剪灌木 5.x进制的减法 6.统计子矩阵 7.积木画 8.扫雷 9.李白打酒 10.砍竹子 1.九进制转换十进制 题目解析&#xff1a;就是将2022的每一位拿出来乘以9的n-1次方的和就是最终答案。次方是从0开始的到n-1. #include &…

蓝桥杯物联网竞赛_STM32L071_16_EEPROM

仍然是没有考过的知识点 朴素的讲就是板子中一块不会因为断电重启而导致数值初始化的一片地址 要注意的是有时候容易把板子什么写错导致板子什么地址写坏了导致程序无法烧录&#xff0c;这个时候记得一直按flash键烧录&#xff0c;烧录时会报错&#xff0c;点击确定&#xff0…

什么是MOV视频格式?如何把MP4视频转MOV视频格式?

一&#xff0c;前言 当然可以&#xff0c;MP4视频可以转换为MOV格式。这两种格式都是常见的视频文件格式&#xff0c;它们都可以用于存储和播放视频内容。虽然它们的编码方式和特性有所不同&#xff0c;但使用合适的视频转换工具可以轻松地将MP4视频转换为MOV格式。 二&#…

【洛谷 P8804】[蓝桥杯 2022 国 B] 故障 题解(概率论+条件概率+贝叶斯公式)

[蓝桥杯 2022 国 B] 故障 题目描述 在软件或系统开发中&#xff0c;我们会遇到各种各样的故障。为了从故障现象反推故障原因&#xff0c;工程师们会总结一种叫做相关性矩阵的二维表格&#xff0c;来表示故障原因与故障现象之间的关系。比如: 其中每行表示一种故障原因&#x…

bugku-web-你从哪里来

这里就这一句话提示&#xff0c;问我是不是谷歌的&#xff1f; 用谷歌浏览器访问 没看见什么变化 抓包查看 没有变化 这时我想到爬虫中的反爬策略中有一种&#xff0c;判断请求的当前界面来判断用户的起始判断位置 这时抓取报文 GET / HTTP/1.1 Host: 114.67.175.224:1516…

Ollama教程——兼容OpenAI API:高效利用兼容OpenAI的API进行AI项目开发

相关文章: Ollama教程——入门&#xff1a;开启本地大型语言模型开发之旅 Ollama教程——模型&#xff1a;如何将模型高效导入到ollama框架 Ollama教程——兼容OpenAI API&#xff1a;高效利用兼容OpenAI的API进行AI项目开发 Ollama教程——兼容OpenAI API&#xff1a;高效利用…

阿里云服务器项目部署docker-compose+vue+redis+nginx+minio+springboot

1 阿里云服务器项目部署-单机部署 docker-compose 1.1 搭建背景 服务器 阿里云服务器 前端 vue 后端 springboot 服务 redis 、nginx、minio 都做单机模式部署,不做集群部署 博客内容参考了其他博文&#xff0c;会贴出来 1.2 <重要>端口开放前提说明 任何开放的端…

(3)(3.1) 英特尔Realsense深度摄像头(三)

文章目录 前言 10 系统概述 11 手动设置配套计算机 前言 本文介绍如何将英特尔 Realsense 深度摄像头(Intel Realsense Depth Camera)与 ArduPilot 配合使用&#xff0c;以实现避障(obstacle avoidance)。该方法使用在配套计算机上运行的 Python 脚本&#xff08;非 ROS&am…

【算法】模拟

个人主页 &#xff1a; zxctscl 如有转载请先通知 题目 前言1. 1576. 替换所有的问号1.1 分析1.2 代码 2. 495. 提莫攻击2.1 分析2.2 代码 3. 6. Z 字形变换3.1 分析3.2 代码 4. 38. 外观数列4.1 分析4.2 代码 5. 1419. 数青蛙5.1 分析5.2 代码 前言 模拟算法就是根据题目所给…

怎么开发一个预约小程序_一键预约新体验

预约小程序&#xff0c;让生活更便捷——轻松掌握未来&#xff0c;一键预约新体验 在快节奏的现代生活中&#xff0c;我们总是在不断地奔波&#xff0c;为了工作、为了生活&#xff0c;不停地忙碌着。然而&#xff0c;在这繁忙的生活中&#xff0c;我们是否曾想过如何更加高效…

【力扣】101. 对称二叉树

101. 对称二叉树 题目描述 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false 提示…

什么是云原生

什么是云原生 云原生的定义 aws&#xff1a; 云原生是在云计算环境中构建、部署和管理现代应用程序的软件方法。现代公司希望构建高度可伸缩、灵活和有弹性的应用程序&#xff0c;以便能够快速更新以满足客户需求。为此&#xff0c;他们使用了支持云基础设施上应用程序开发的现…

基于YOLOv9的道路缺陷检测,加入DCNv4、自适应阈值焦点损失提升检测精度

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文内容&#xff1a;针对基于YOLOv9的道路缺陷检测进行性能提升&#xff0c;加入各个创新点做验证性试验。 DCNv4结合SPPELAN&#xff1a;mAP从原始的0.923 提升至0.935 自适应阈值焦点损失&#xff1a; mAP从原始的0.923 提升至0.93…

Mysql视图与事物与字符集实验

一 视图 1.视图的定义 视图是一个虚拟表&#xff0c;其内容由查询定义。 2.视图的优点 1&#xff09;视点集中 2&#xff09;简化操作 3&#xff09;定制数据 4&#xff09;分隔合并数据 5&#xff09;安全性好 3.语法格式及限定条件 1&#xff09;语法格式&#xff1…