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

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。

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

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

相关文章

Leecode热题100---45:跳跃游戏②

题目&#xff1a; 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。 返回到达 nums[n - 1] 的最小跳跃次数。 思路&#xff1a; 如果某一个作为 起跳点 的格子可以跳跃的距离是 3&#xff0c;那么表示后面…

127.数据异构方案

文章目录 前言一、数据异构的常用方法1. 完整克隆2. MQ方式3. binlog方式 二、MQ与Binlog方案实现MQ方式binlog方式注意点 三、总结 前言 何谓数据异构&#xff1a;把数据按需&#xff08;数据结构、存取方式、存取形式&#xff09;异地构建存储。比如我们将DB里面的数据持久化…

【源码分享】简单的404 HTML页面示例,该页面在加载时会等待2秒钟,然后自动重定向到首页

展示效果 源码 html <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><title>404 页面未找到</title><meta http-equiv"refresh" content"2;url/"> <!-- 设置2秒后跳转到首…

适合小白入门的AI扩图(创成式填充)工具

近期&#xff0c;发现许多人对AI扩图工具的需求比较大&#xff0c;为了满足大家的需求&#xff0c;本期天祺为大家整理了一些好用的AI扩图工具&#xff0c;各个设配的扩图工具都有介绍哦&#xff0c;电脑&#xff0c;手机端都能用&#xff0c;大家可以根据自己的喜好和需求进行…

1075: 求最小生成树(Prim算法)

解法&#xff1a; 总结起来&#xff0c;Prim算法的核心思想是从一个顶点开始&#xff0c;一步一步地选择与当前最小生成树相邻的且权值最小的边&#xff0c;直到覆盖所有的顶点&#xff0c;形成一个最小生成树。 #include<iostream> #include<vector> using names…

Kubernetes 应用滚动更新

Kubernetes 应用版本号 在 Kubernetes 里&#xff0c;版本更新使用的不是 API 对象&#xff0c;而是两个命令&#xff1a;kubectl apply 和 kubectl rollout&#xff0c;当然它们也要搭配部署应用所需要的 Deployment、DaemonSet 等 YAML 文件。 在 Kubernetes 里应用都是以 …

力扣HOT100 - 169. 多数元素

解题思路&#xff1a; 有点类似于Boyer-Moore 投票算法&#xff0c;但更加形象。 class Solution {public int majorityElement(int[] nums) {int winner nums[0];int cnt 1;for (int i 1; i < nums.length; i) {if (winner nums[i]){cnt;} else if (cn…

Redis每月运维

为防止redis自动aof缩放失败 每月手动执行一次重写命令 bgrewriteaof 方式一&#xff1a; redis-cli 连接到每个服务器 认证后执行bgrewriteaof 示例 方式二&#xff1a; 通过工具连接到redis 执行命令 方式三: 定时任务系统 在定时任务系统里每天自动执行gocron - 定时任务…

基于transformers框架实践Bert系列5-阅读理解(文本摘要)

本系列用于Bert模型实践实际场景&#xff0c;分别包括分类器、命名实体识别、选择题、文本摘要等等。&#xff08;关于Bert的结构和详细这里就不做讲解&#xff0c;但了解Bert的基本结构是做实践的基础&#xff0c;因此看本系列之前&#xff0c;最好了解一下transformers和Bert…

基于SpringBoot和Hutool工具包实现的验证码案例

目录 验证码案例 1. 需求 2. 准备工作 3. 约定前后端交互接口 需求分析 接口定义 4. Hutool 工具介绍 5. 实现验证码 后端代码 前端代码 6. 运行测试 验证码案例 随着安全性的要求越来越高&#xff0c;目前项目中很多都会使用验证码&#xff0c;只要涉及到登录&…

一个用Java编写的屏幕测距工具,包括游戏地图测量功能

该程序提供了一个简单便捷的方式&#xff0c;在屏幕上测量距离&#xff0c;包括游戏地图分析在内。它允许用户准确确定屏幕上两点之间的距离&#xff0c;帮助游戏过程中的战略规划、资源管理和决策制定。 特点&#xff1a; 简单易用的界面&#xff1a;直观的控制使测量距离变得…

Marin说PCB之POC电路layout设计仿真案例---03

今天天中午午休的时候&#xff0c;我刚要打开手机的准备刷抖音看无忧传媒的学生们的“学习资料”的时候&#xff0c;看到CSDN -APP上有提醒&#xff0c;一看原来是一位道友发的一个问题&#xff1a; 本来小编最近由于刚刚从国外回来&#xff0c;手上的项目都已经结束了&#xf…

MQTT到串口的转发(node.js)

本文针对以下应用场景&#xff1a;已有通过串口通信的设备或软件&#xff0c;想要实现跨网的远程控制。 node.js安装 从 Node.js — Run JavaScript Everywhere下载LTS版本安装包&#xff0c;运行安装程序。&#xff08;傻瓜安装&#xff0c;按提示点击即可&#xff09; 设置环…

忍の摸头之术游戏娱乐源码

本资源提供给大家学习及参考研究借鉴美工之用&#xff0c;请勿用于商业和非法用途&#xff0c;无任何技术支持&#xff01; 忍の摸头之术游戏娱乐源码&#xff0c;抖音上面非常火的摸头杀画面,看得我眼花缭乱,源码拿去玩吧&#xff1b; 目录说明 忍の摸头之术&#xff1a;域…

idea新建项目/模块找不到Spring Initializr

idea创建项目找不到spring intellij&#xff0c;如下图解决 可能是没有下载spring的相应插件&#xff0c;或者没有启用对应的插件 我这里就是没有启用插件&#xff0c;导致的创建项目时找不到按件。 全部启用后&#xff0c;重启idea即可。 重启后可以看到出现了“Spring Initi…

【Andoird开发】android获取蓝牙权限,beacon,android-beacon-library

iBeacon 最先是苹果的技术&#xff0c;使用android-beacon-library包可以在android上开发iBeacon 技术。 iBeacon的发明意义重大。它是一种基于蓝牙低功耗&#xff08;Bluetooth Low Energy, BLE&#xff09;技术的定位系统&#xff0c;通过向周围发送信号来标识其位置。这项技…

Docker 容器间通讯

1、虚拟ip/访问 同一网络 安装docker时&#xff0c;docker会默认创建一个内部的桥接网络docker0&#xff0c;每创建一个容器分配一个虚拟网卡&#xff0c;容器之间(包括宿主机)可以根据分配的ip互相访问(ps:其他主机(包括其他主机的容器)无法ping通docker容器ip无法访问&#…

22个C语言小白常见问题总结

一.语言使用错误 在打代码的过程中&#xff0c;经常需要在中文与英文中进行转换&#xff0c;因此常出现一些符号一不小心就用错&#xff0c;用成中文。例如&#xff1a;“&#xff1b;”中文中的分号占用了两个字节&#xff0c;而英文中“;”分号只占用一个字节。编译器只能识…

mysql数据库主从复制,搭建从库

1 期望效果 假设我们现在有两个服务器&#xff0c;两个服务器都有数据库&#xff0c;然后我们命名一个叫主数据库&#xff08;Master&#xff09;&#xff0c;一个叫从数据库&#xff08;Slave&#xff09; 数据备份和容灾&#xff1a;通过主从复制&#xff0c;可以将主数据库…

计算机操作系统核心组件

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天给大家讲讲操作系统。 操作系统核心组件 用户借助于一个或多个应用程序与操作系统进行交互&#xff0c;常常是通过一个称为shell的特殊应用程序进行的&#xff0c;shell也叫作命令解释器。105今天的大多…