代码随想录算法训练营第三天| 203.移除链表元素、 707.设计链表、 206.反转链表

news2024/11/24 19:41:21

203.移除链表元素

在这里插入图片描述

题目链接: 203.移除链表元素
文档讲解:代码随想录
状态:没做出来,做题的时候定义了一个cur指针跳过了目标val遍历了一遍链表,实际上并没有删除该删的节点。

错误代码:

    public ListNode removeElements(ListNode head, int val) {
        ListNode sentinel = new ListNode();
        sentinel.next = head;
        ListNode cur = sentinel;
        while (cur.next != null) {
            ListNode next = cur.next;
            if (next.val == val) {
                // 这里是错误的,cur 被设置为 next.next,直接跳过了 next 节点。但这并没有实际从链表中删除 next 节点,它仍然存在于链表中。
                // 如果 next.next 是 null,则 cur 被设置为 null,导致后续的 cur.next 会抛出空指针异常
                cur = next.next;
            } else {
                cur = next;
            }
        }
        return sentinel.next;
    }

思路:定义一个ListNode类型的cur指针,遍历链表,遇到等于目标val的节点,通过修改节点的next实现删除元素。因为NodeList是引用类型,所以当cur从虚拟头节点出发时,修改cur中的引用会影响虚拟头节点后面的值,从而实现删除操作。

题解

    public ListNode removeElements(ListNode head, int val) {
        // 创建一个哨兵节点,简化边界情况的处理
        ListNode sentinel = new ListNode();
        sentinel.next = head;
        ListNode cur = sentinel;
        // 遍历链表
        while (cur.next != null) {
            ListNode next = cur.next;
            // 如果下一个节点的值等于指定值
            if (next.val == val) {
                // 应该通过修改cur.next来删除next节点
                cur.next = next.next;
            } else {
                // 否则,继续向后遍历
                cur = next;
            }
        }
        // 返回处理后的链表头节点
        return sentinel.next;
    }

什么时候使用虚拟头节点?
虚拟头节点(哨兵节点)主要解决了由于头节点可能为空或者需要特别处理而导致的额外操作。
如果在使用虚拟头节点后仍然从 head 节点出发,那么虚拟头节点的定义就失去了意义。

所以,当定义了虚拟头节点(哨兵节点)后,一般情况下,遍历操作中的 cur(当前节点)都从虚拟头节点出发。这是因为虚拟头节点是为了简化处理头节点的特殊情况而引入的,从虚拟头节点开始遍历,可以统一处理所有节点(包括原本的头节点),避免了额外的边界条件检查。

707.设计链表

在这里插入图片描述

题目链接:707.设计链表
文档讲解:代码随想录
状态:没做出来(使用了双向链表,没有使用虚拟头节点,结果一堆边界问题。。。)

题解

单向链表+虚拟头节点题解

public class MyLinkedList {

    private ListNode dummy; // 虚拟节点
    private int size;       // 链表的长度

    /** 初始化链表 */
    public MyLinkedList() {
        dummy = new ListNode(0);
        size = 0;
    }

    /** 获取链表中下标为 index 的节点的值,如果下标无效则返回 -1 */
    public int get(int index) {
        if (index < 0 || index >= size) {
            return -1;
        }

        ListNode current = dummy.next;
        for (int i = 0; i < index; i++) {
            current = current.next;
        }
        return current.val;
    }

    /** 在链表头部插入值为 val 的节点 */
    public void addAtHead(int val) {
        ListNode newHead = new ListNode(val);
        newHead.next = dummy.next;
        dummy.next = newHead;
        size++;
    }

    /** 在链表尾部追加值为 val 的节点 */
    public void addAtTail(int val) {
        ListNode newTail = new ListNode(val);
        ListNode current = dummy;
        while (current.next != null) {
            current = current.next;
        }
        current.next = newTail;
        size++;
    }

    /** 在链表中下标为 index 的节点之前插入值为 val 的节点 */
    public void addAtIndex(int index, int val) {
        if (index < 0 || index > size) {
            return;
        }
// 注意:不是ListNode current = dummy.next;
// 简而言之,ListNode cur = dummy; 确保了在插入节点时,从链表的头节点开始遍历,而不是跳过头节点。
// 例如index=0,则是dummy.next = newNode
        ListNode current = dummy;
        for (int i = 0; i < index; i++) {
            current = current.next;
        }
        ListNode newNode = new ListNode(val);
        newNode.next = current.next;
        current.next = newNode;
        size++;
    }

    /** 删除链表中下标为 index 的节点 */
    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }

        ListNode current = dummy;
        for (int i = 0; i < index; i++) {
            current = current.next;
        }
        current.next = current.next.next;
        size--;
    }

    static class ListNode {
        int val;
        ListNode next;

        // 节点构造函数
        public ListNode(int val) {
            this.val = val;
        }
    }
}

双向链表无虚拟头节点题解(不推荐)

class MyLinkedList {

    ListNode head;

    static class ListNode {
        int val;
        ListNode pre;
        ListNode next;

        public ListNode() {
        }

        public ListNode(int val, ListNode pre, ListNode next) {
            this.val = val;
            this.pre = pre;
            this.next = next;
        }

        @Override
        public String toString() {
            return "ListNode{" +
                    "val=" + val +
                    ", pre=" + pre +
                    ", next=" + next +
                    '}';
        }
    }

    public MyLinkedList() {
        head = null;
    }

    public int get(int index) {
        if (index < 0 || head == null) {
            return -1;
        }
        ListNode cur = head;
        while (index > 0 && cur != null) {
            cur = cur.next;
            index--;
        }
        return (cur == null) ? -1 : cur.val;
    }

    public void addAtHead(int val) {
        ListNode node = new ListNode(val, null, head);
        if (head != null) {
            head.pre = node;
        }
        head = node;
    }

    public void addAtTail(int val) {
        ListNode node = new ListNode(val, null, null);
        ListNode cur = head;
        while (cur.next != null) {
            cur = cur.next;
        }
        cur.next = node;
        node.pre = cur;

    }

    public void addAtIndex(int index, int val) {
        if (index < 0) {
            return;
        }
        if (index == 0) {
            addAtHead(val);
            return;
        }
        ListNode cur = head;
        while (index > 1 && cur != null) {
            cur = cur.next;
            index--;
        }
        if (cur == null) {
            return;
        }
        ListNode node = new ListNode(val, cur, cur.next);
        if (cur.next != null) {
            cur.next.pre = node;
        }
        cur.next = node;
    }

    public void deleteAtIndex(int index) {
        if (index < 0 || head == null) {
            return;
        }
        if (index == 0) {
            head = head.next;
            if (head != null) {
                head.pre = null;
            }
            return;
        }
        ListNode cur = head;
        while (index > 0 && cur != null) {
            cur = cur.next;
            index--;
        }
        if (cur == null) {
            return;
        }
        if (cur.pre != null) {
            cur.pre.next = cur.next;
        }
        if (cur.next != null) {
            cur.next.pre = cur.pre;
        }
    }
}

206.反转链表

在这里插入图片描述

题目链接: 206.反转链表
文档讲解:代码随想录
状态:没做出来,明明很简单的题,为啥没做出来呢。。。。。想着用dummy节点,dummy->1->2->3… dummy->2->1->3… dummy->3->2->1->…,这个dummy的next一直在变。。。

思路:

在这里插入图片描述

题解

    public ListNode reverseList(ListNode head) {
        // 初始化前一个节点为null
        ListNode prev = null;
        // 当前节点从head开始
        ListNode curr = head;

        while (curr != null) {
            // 暂时保存下一个节点
            ListNode next = curr.next;
            // 当前节点的next指向前一个节点
            curr.next = prev;
            // 移动前一个节点到当前节点
            prev = curr;
            // 移动当前节点到下一个节点
            curr = next;
        }
        // 最后prev将指向新的头节点
        return prev;
    }

总结反思

为啥做题的时候总是有很多乱七八糟的思路,自己找的还总是最差的那种呢。。。。

链表题解的两个技巧
遇到链表相关的题,无论问题是什么,先要想想是不是可以用上以下的两个技巧。

  1. 哨兵节点:哨兵节点是一个非常常用的链表技巧,在处理链表边界问题的场景下,可以减少我们代码的复杂度。
    主要解决的问题如下:
    • 处理完一条链表后,需要返回这个链表的头结点。我们在一开始的时候使用哨兵节点(dummy),让它的 next 节点指向 head 节点。最后 return 时直接返回 dummy.next 即可。
    • 在对链表进行插入或删除节点时,使用哨兵节点可以简化删除 head 节点或者向 head 前插入节点时的处理逻辑。因为头节点没有前驱节点,这就导致对其的增删需要额外操作。
    • 在某些遍历链表的时候,可能会需要同时记录 pre 节点。当你从 head 节点开始遍历时,head 是没有 pre 节点的(为null)。而此时引用哨兵节点,相当于帮助 head 节点初始化了一个 pre 节点,可以方便的解决 pre 节点为空的问题

为啥反转链表这道题不用虚拟头结点呢?

对于反转链表的操作,无论是递归还是迭代方法,都集中在反转节点的指针上,而不涉及节点的插入或删除操作。因此,反转链表的核心在于调整指针的方向,而不是处理节点的前驱节点或边界条件。这也是为什么在反转链表时,虚拟头节点并不能带来实际的简化。

  1. 双指针:其实不止是链表,对于数组题来说,也是非常好用的技巧。
    双指针的主要用法有以下几种:
    • 两个指针在两条链表上行走:这种方法通常用于合并两个有序链表、找到两个链表的交点等问题。
    • 快慢指针,同时前进:快慢指针通常用于检测链表中是否存在环,或者找到链表的中间节点等情况。
    • 前后指针,前指针先走 n 步,之后两个指针同时前进:这种方法常用于找到链表的倒数第 n 个节点,或者解决某些数组问题。
    • 一个指针用来迭代遍历链表,另一个指针用来记录:在链表操作中,常常需要用一个指针来迭代遍历链表,另一个指针用来记录节点,例如 pre 和 cur;cur 和 next;pre、cur 和 next。

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

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

相关文章

一键恢复安卓手机数据:3个快速简便的解决方案!

安卓手机作为我们不可或缺的数字伙伴&#xff0c;承载着大量珍贵的个人和工作数据。然而&#xff0c;随着我们在手机上进行各种操作&#xff0c;不可避免地会遇到一些令人头痛的问题&#xff0c;比如意外删除文件、系统故障或其他不可预见的情况&#xff0c;导致重要数据的丢失…

springboot基于Web前端技术的java养老院管理系统_utbl7

3.普通用户模块包括&#xff1a;普通会员的注册、养老院客房查询、养老院留言查询、预约老人基本信息登记、选择房间、用户缴费的功能。 4.数据信息能够及时进行动态更新&#xff0c;增删&#xff0c;用户搜素方便&#xff0c;使用户可以直接浏览相关信息&#xff0c;要考虑便于…

埋点——about前端

所谓“埋点”&#xff0c;是数据采集领域(尤其是用户行为数据采集领域)的术语&#xff0c;指的是针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。比如用户某个icon点击次数、观看某个视频的时长等等,埋点的技术实质&#xff0c;是先监听软件应用运行过程中…

C#数据类型变量、常量

一个变量只不过是一个供程序操作的存储区的名字。 在 C# 中&#xff0c;变量是用于存储和表示数据的标识符&#xff0c;在声明变量时&#xff0c;您需要指定变量的类型&#xff0c;并且可以选择性地为其分配一个初始值。 在 C# 中&#xff0c;每个变量都有一个特定的类型&…

只需提交几次代码就能轻松实现系统级的变更!——“基础设施即代码”模式与实践

“基础设施即代码”模式与实践 基础设施即代码&#xff08;Infrastructure as Code&#xff0c;IaC&#xff09;是指利用脚本、配置或编程语言创建和维护基础设施的一组实践和流程。通过IaC&#xff0c;我们可以轻松测试各个组件、实现所需的功能并在最小化停机时间的前提下进行…

51-指针_野指针,指针运算

51-1 野指针 51-1-1 什么是野指针 概念&#xff1a;野指针就是指针指向的位置是不可知的&#xff08;随机的、不正确的、没有明确限制的) 没有初始化 int main() {int* p;//p没有初始化&#xff0c;就意味着没有明确的指向//一个局部变量不初始化的话&#xff0c;放的是随机…

一文说透组织中的利润中心建设

当前&#xff0c;规模型企业越来越重视利润中心建设&#xff0c;华为的实践表明&#xff0c;建好了利润中心&#xff0c;利润自然好&#xff0c;组织也会上台阶。很多企业利润上不去&#xff0c;就是没有搞好利润中心这个火车头。然而&#xff0c;很多人误以为利润中心只是财务…

Cesium与Three相机同步(2)

之前实现了将Three相机同步到Cesium相机Cesium与Three相机同步(1)-CSDN博客 现在是将Cesium相机同步到Three相机,从而实现了相机双向同步。 <!DOCTYPE html> <html lang="en"><head><title>three.js webgl - orbit controls</title&g…

python:pycharm虚拟解释器报错环境位置目录为空

目录 解释器分控制台解释器 和 pycharm解释器 控制台解释器切换&#xff1a; pycharm解释器 解释器分控制台解释器 和 pycharm解释器 控制台解释器切换&#xff1a; 切换到解释器下 激活解释器 查看解释器 where python 激活成功 这时在控制台使用python xxx.py 可以…

OpenCV:入门(五)

图像梯度 图像梯度计算的是图像变化的速度。对于图像的边缘部分&#xff0c;其灰度值变化较大&#xff0c;梯度值也 较大&#xff1b;相反&#xff0c;对于图像中比较平滑的部分&#xff0c;其灰度值变化较小&#xff0c;相应的梯度值也较小。一般情 况下&#xff0c;图像梯度计…

k8s 1.28.10 浏览器访问6443查看api,需要证书

添加证书 使用client-certificate-data和client-key-data生成一个p12文件 1.生成client-certificate-data grep client-certificate-data ~/.kube/config | head -n 1 | awk {print $2} | base64 -d >> kubecfg.crt2.生成client-key-data grep client-key-data ~/.kub…

深 渔博会成功举办 新文件促进城市数字化转型| 产业互联网观察174期

深圳渔博会水产品竞价交易会成功举办 首批数据跨境一般数据清单发布 新文件加快城市全域数字化转型进程 | 产业互联网观察第174期 2024深圳渔博会水产品竞价交易会成功举办 5月17日至18日&#xff0c;深圳渔博会上深圳国际金枪鱼交易有限公司在深圳会展中心成功举办了水产品竞…

使用Prometheus + Blackbox-exporter快速监控一个网站性能和SSL过期时间

使用blackbox-exporter快速监控一个网站性能和SSL过期时间 环境介绍什么是blackbox-exporter下载blackbox-exporter安装blackbox-exporter配置Prometheus服务端查看job上线监控面板参考文献 环境介绍 本文实验环境 操作系统&#xff1a;Centos 7.9Prometheus版本&#xff1a;…

U-Mail邮件系统为用户提供更加安全的数据保护机制

据外媒报道&#xff0c;近日美国国家安全委员会泄露了其成员的近1万封电子邮件和密码&#xff0c;暴露了政府组织和大公司在内的2000家公司。其中包括美国国家航空航天局和特斯拉等。报道称该漏洞于3月7日被研究人员发现&#xff0c;通过该漏洞攻击者能够访问对web服务器操作至…

哪款电脑桌面日历记事本软件好用?推荐优秀的电脑日历记事本

对于众多上班族而言&#xff0c;每天在电脑前忙碌已成为生活常态。若想提升工作效率&#xff0c;简化繁琐的工作流程&#xff0c;选择一款出色的电脑桌面日历与记事本软件就显得至关重要。 然而&#xff0c;在Windows操作系统上设定提醒显得相当繁琐&#xff0c;而系统自带的记…

创新力作 焕新首发丨捷顺科技·捷曜系列智慧停车新品全新上市

2024捷顺科技智慧停车全家族新品全面上市 全新外观、全新特性、全新体验 新控制机、新道闸、新超眸相机... 每款新品都有哪些功能亮点 带您一探究竟

影响程序员发展,首个关于“软件供应链安全”国家标准发布,你该知道的10个问题!【附标准全文】

近日&#xff0c;GB/T 43698-2024《网络安全技术 软件供应链安全要求》作为国内首个软件供应链安全的国标&#xff0c;对于程序员的影响深远。该标准的实施&#xff0c;不仅为程序员提供了明确的软件安全开发指导&#xff0c;还强化了他们在软件开发过程中对安全性的重视。程序…

如何解决Nginx反向代理不生效?

目录 背景 过程 日志 检查配置文件 重启服务 检查容器内的配置文件 容器和宿主机 其他 背景 用了两年的nginx新加的反向代理不生效 Docker挂载的配置文件启动的Nginx&#xff0c;配置一切正常&#xff0c;但是反向代理不生效&#xff0c;???先自查一波 过程 日志 …

废物回收机构|基于SprinBoot+vue的地方废物回收机构管理系统(源码+数据库+文档)

地方废物回收机构管理系统 目录 基于SprinBootvue的地方废物回收机构管理系统 一、前言 二、系统设计 三、系统功能设计 1管理员功能模块 2 员工功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍…

【ZYNQ】GPIO 与 AXI GPIO

在嵌入式开发中&#xff0c;GPIO 是最常见的外设。GPIO 是 General Purpose I/O 的缩写&#xff0c;译为通用输入/输出。GPIO 用于连接外部设备&#xff0c;例如按键、传感器等&#xff0c;实现数字信号的输入或输出功能。本文主要介绍 Zynq GPIO 的基本概念&#xff0c;并对比…