力扣 单链表元素删除解析及高频面试题

目录

删除元素的万能方法

构造虚拟头结点来应对删除链表头结点的情况

一、203.移除链表元素

题目

题解

二、19.删除链表中倒数第K个节点

题目

题解

三、

83.删除某个升序链表中的重复元素,使重复的元素都只出现一次 

题目

题解

82.删除某个升序链表中的重复元素且不保留,即只含原始链表中没有重复出现过的元素

题目

题解


删除元素的万能方法

删除元素的万能方法,就是找到目标节点,保存目标节点的前驱节点的引用,让前驱结点的next,指向目标节点的后继节点。这样,没有目标节点的引用,它将被gc回收。

  • 这里cur是前驱节点的引用,cur.next指向目标节点。删除元素步骤如下:首先我们在题目中通过各种判断,确定cur.next指向的,就是我们需要删除的目标节点 。然后,把cur.next.next赋值给cur.next。这也就是说,让cur.next也指向后继结点。
  • 此时前驱结点的next已经指向了后继结点。那原来的目标节点呢?已经不连接在链表中了,并因其没有引用变量指向,将会被gc回收。

但这个时候有些细心的小伙伴可能会有疑问:你这删除是讲清楚了,但是我发现这样的删除需要有前驱节点和后继节点,那如果是删除链表头结点(没有前驱结点)或是删除链表尾结点(没有后继结点),那怎么办呢?哈哈,先告诉结论:链表尾结点的状况,上述做法可以包含进来。而链表头结点,要么分情况讨论,要么使用我们接下来使用的方法:建立虚拟链表头结点。

构造虚拟头结点来应对删除链表头结点的情况

在上文中我们得出尾结点是包含在cur.next = cur.next.next中的。但是对于头结点就不好使了。为什么呢?因为如果后继结点是null,那还可以把null赋给一个引用,但是如果前驱结点cur为null,那么cur.next就会直接抛出一个异常了!那我们怎么办呢?第一种方法是if-else判断,如果删除元素是头结点就直接返回head.next。但是这样写的话代码比较乱,这里介绍一种更好的办法:构造虚拟头结点。这是啥意思呢?所谓虚拟头结点,就是在原链表的头结点前方接上一个节点。这样,原链表的所有结点在新链表中都不是头结点了,自然就可以沿用上面的赋值式。

如图,先创建虚拟头结点temp(值设为-1),然后让它的next指向原链表头结点,然后让cur初始化为temp(cur = temp),这样cur就永不为null,删除节点永远有前驱节点,cur.next = cur.next.next就可以继续使用。

这样我们就成功用cur.next = cur.next.next完成了头结点的删除。

需要注意的是,此方法到最后,返回删除元素后的新链表的头结点时,应该返回的是temp.next,而不是temp。

一、203.移除链表元素

题目

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

题解

通过在头节点前增加虚拟头节点(哨兵),头结点则变为普通结点,则不需要判断头节点是否为待删除的节点,但是在返回的时候需要返回的是虚拟头节点的下一节点而不是虚拟头节点。

public class ListNode {
    public int val;
    public ListNode next;
}
public static ListNode deleteListNode(ListNode head,int val){
    //删除所有val结点,删除节点的方式是找到前驱节点cur,让cur.next = cur.next.next
    ListNode temp = new ListNode(-1);
    temp.next = head;
    ListNode cur = temp;//构造了带虚拟头结点的链表并初始化cur
    //用cur.next.val判断是因为cur.next.val == val判断cur.next是否为目标节点,
    //这样cur就是前驱结点的引用了。我们在删除节点时必须考虑到保留前驱结点引用。
    while (cur.next != null){
        if (cur.next.val == val){
            cur.next = cur.next.next;//删除目标节点。cur不移动,继续与下一个节点比较
        }else {
            cur = cur.next;//如果当前cur.next不符合删除要求,cur 向链表后方移动一次再次比较
        }
    }
    return temp.next;
}

二、19.删除链表中倒数第K个节点

题目

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

提示:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz

题解

这里我们使用双指针法,先让快慢指针步差为K+1,然后快慢指针同步移动,这样当快指针指向链表末尾的null时(你也可以理解成链表最后一个元素是倒数第1节点,最后一个元素的null指针域是倒数第0节点),即快指针指向倒数第0个节点时,因为步差K+1,所以这时慢指针指向倒数第K+1个节点,也就是前驱节点。不知道你发现没有,2)题这类问题的核心,就是在于寻找删除节点的前驱结点。

然后按部就班的删除即可。

public static ListNode deleteLastKByTwoPointers(ListNode head,int k){
    ListNode temp = new ListNode(-1);
    temp.next = head;
    ListNode slow = temp;
    ListNode fast = temp;
    //初始化,虚拟头结点构造,快慢指针指向虚拟头结点
    //fast先走k+1步
    for (int i = 0; i < k+1; i++) {
        fast = fast.next;
    }
    while (fast != null){
        fast = fast.next;
        slow = slow.next;
    }//同步前进直到fast为null
    slow.next = slow.next.next;//删除待删除的节点
    return temp.next;
}

三、

这类问题的关键在于你的cur引用是指向第一个重复元素,还是指向第一个重复元素的前驱结点。

(1)如果cur指向第一个重复元素,那么把cur.val和cur.next.val比较,如果相等就删除cur.next,这样就删除了所有和cur.val相同值的节点,但还留下了一个cur节点。这样会留下一个重复节点,即LeetCode83。

  • cur.val==cur.next.val ?删除cur.next:cur向后移动
  • 最后重复元素留下一个节点0

(2)如果cur指向第一个重复元素的前驱节点,那么把cur.next.val和cur.next.next.val比较,如果相等就存下重复元素的值(6),然后cur.next.val逐个与存储值比较,是就删除cur.next,这样就删除了所有重复结点。这样重复值节点一个也不会留下,即LeetCode82。

  • 如果cur.next.val==repeatData,循环删除直到没有repeatData节点为止

83.删除某个升序链表中的重复元素,使重复的元素都只出现一次 

题目

给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。

提示:
链表中节点数目在范围 [0, 300] 内
-100 <= Node.val <= 100
题目数据保证链表已经按升序 排列

题解

由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,这个很关键。因此我们只需要对链表进行一次遍历,就可以删除重复的元素。

public static ListNode deleteRepeatAndSaveOne(ListNode head){
    if (head == null){
        return null;
    }
//这题不用构建虚拟头结点
    ListNode cur = head;
    while (cur.next != null){
        //逐个逐个比较直到后继结点为null
        if (cur.val == cur.next.val){
            //如果值相同,则删除cur.next, cur不移动,和下一个cur.next继续比较
            cur.next = cur.next.next;
        }else {
            cur = cur.next;//如果值不同,则移动cur至后继结点,开始下一轮比较
        }
    }
    return head;
}

82.删除某个升序链表中的重复元素且不保留,即只含原始链表中没有重复出现过的元素

题目

给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。

提示:
链表中节点数目在范围 [0, 300] 内
-100 <= Node.val <= 100
题目数据保证链表已经按升序 排列

题解

这道题和上一道题类似83. 删除排序链表中的重复元素,但是这里要求的是删除所有重复的链表节点,而上一题可以保留一个。
分析一下这道题,删除所有重复的节点,加入有两个节点被删除,例如[1,2,2,3],那么在进行对比的时候需要保留一个节点在1的位置,寻找重复的两个指针一个左边的在第一个2,右边的第一次在第二个2,第二次在3。这个时候我们需要把1->3连接起来。

流程如下:

  1. 一边遍历、一边统计相邻节点的值是否相等,如果值相等就继续后移找到值不等的位置,然后删除值相等的这个区间。
    1. 设置一个虚拟节点,将虚拟头节点和原链表头节点连接起来
    2. 从虚拟头节点位置开始访问
    3. 只要当前访问节点的下一个节点与下下个节点都存在,就继续访问下去
    4. 在访问过程中,如果下一个节点与下下个节点相同,那么说明与这个节点值相同的所有节点都应该被删除掉。
  2. 删除的方法是先记录这个值,利用 while 循环,不断的查找出那些相同的节点值来,每次找到了一个相同的值,那么当前访问的节点 cur 就越过这个节点。
  3. 在访问过程中,下一个节点与下下个节点不相同,说明 cur 可以加入到最终的结果链表中,那么继续访问后面的节点。

 代码如下:

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        // 一开始设置一个虚拟节点,它的值为 -1,它的值可以设置为任何的数,因为我们根本不需要使用它的值
        ListNode dummy = new ListNode(-1);
        // 将虚拟头节点和原链表头节点连接起来
        // 添加虚拟头节点之后,原链表的每个节点的地位都是一样的
        dummy.next = head;
        // 从虚拟头节点位置开始访问
        ListNode cur = dummy;

        // 只要当前访问节点的下一个节点与下下个节点都存在,就继续访问下去
        while (cur.next != null && cur.next.next != null) {
            // 在访问过程中,会出现两种情况
            // 1、下一个节点与下下个节点相同,那么说明与这个节点值相同的所有节点都应该被删除掉
            if (cur.next.val == cur.next.next.val) {
                // 删除的方法是先记录这个值
                int value = cur.next.val;
                // 利用 while 循环,不断的查找出那些相同的节点值来
                while (cur.next != null && cur.next.val == value) {
                    // 每次找到了一个相同的值,那么当前访问的节点 cur 就越过这个节点
                    cur.next = cur.next.next;
                }
                // 2、下一个节点与下下个节点不相同,说明 cur 可以加入到最终的结果链表中
            } else {
                // 那么继续访问后面的节点
                cur = cur.next;
            }
        }
        // 最终返回虚拟头节点的下一个节点就行了
        return dummy.next;
    }
}

我们发现,其实单链表删除都是一样的套路。找好前驱节点,明晰删除条件,遍历小心谨慎,脑中不断构建图,其实单链表删除真的不难。

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

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

相关文章

玛格家居从深交所转板北交所:营收净利润连年下滑,销售费用大增

《港湾商业观察》施子夫 近日&#xff0c;玛格家居股份有限公司&#xff08;以下简称&#xff0c;玛格家居&#xff09;发布公告&#xff0c;重庆证监局已经受理其北交所上市的备案申请&#xff0c;辅导机构为国泰君安证券。 公开信息显示&#xff0c;2022年1月&#xff0c;玛…

DreamView数据流

DreamView数据流 查看DV中界面启动dag&#xff0c;/apollo/modules/dreamview_plus/conf/hmi_modes/pnc.pb.txt可以看到点击界面的planning按钮&#xff0c;后台其实启动的是/apollo/modules/planning/planning_component/dag/planning.dag和/apollo/modules/external_command…

使用网络抓取器进行网络抓取--你需要了解的一切

什么是网页抓取&#xff1f; 网页抓取是一种计算机化过程&#xff0c;用于从网站上收集大量数据。它也常被称为网页数据提取或网页数据抓取。 网页抓取需要两个部分 - 爬虫和抓取器。 爬虫是一种AI算法&#xff0c;通过跟随互联网中的链接来搜索所需的特定数据。抓取器是一种…

Python对象不可哈希?教你几招解决!

目录 1、什么是可哈希?🚀 1.1 哈希基础理论 1.2 可哈希对象定义🔍 示例代码: 1.3 Python中哈希的作用 1.4 哈希表与性能提升📈 应用实例代码: 2、Python中的哈希特性🔑 2.1 不变性与哈希值🔄 示例代码展示: 2.2 实现细节深入探讨📚 深入代码细节:…

小区服务前台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;住户管理&#xff0c;管理员管理&#xff0c;员工管理&#xff0c;安保管理&#xff0c;安保分配管理&#xff0c;客服聊天管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;公告&#xff0c;…

【传知代码】揭秘AI如何揪出图片中的“李鬼”(论文复现)

在数字化时代&#xff0c;我们时常被各种图像信息所包围。然而&#xff0c;这些图像中有时隐藏着不为人知的秘密——被篡改的文字或图像。这些被篡改的内容可能误导我们的判断&#xff0c;甚至在某些情况下造成严重的后果。幸运的是&#xff0c;随着人工智能&#xff08;AI&…

免费开源AI生产力工具:内置专属ChatGPT、一键智能处理图片和视频(擦除水印、卡通漫画、无损放大、插值补帧、智能修复、3D转制、上色修复、合成整理)

AI 生产力工具 免费开源&#xff0c;提升用户生产力&#xff0c;保障隐私和数据安全。提供高效便捷的AI解决方案&#xff0c;包括但不限于&#xff1a;内置专属ChatGPT、一键批量智能处理图片和视频等。 主要特点 免费开源&#xff1a;免费使用&#xff0c;源代码开放&#…

使用Nginx反向代理KKFileView遇到问题

使用KKFileView 4.0 以上版本 在KKFileView官网上&#xff0c;关于使用Nginx代理&#xff0c;建议配置如下 一、修改Nacos 在Nginx的conf文件夹中修改 nginx.conf ,新加 红框内的IP地址为代理服务器地址&#xff08;即安装KKFileView的服务器地址&#xff09; 二、修改KKFil…

逻辑这回事(七)---- 器件基础

Xilinx FPGA创建了先进的硅模块(ASMBL)架构,以实现FPGA具有针对不同应用程序领域优化的各种功能组合的平台。通过这一创新,Xilinx提供了更多的设备选择,使客户能够为其特定设计选择具有正确的功能和功能组合的FPGA。ASMBL体系结构通过以下方式突破了传统的设计障碍:消除几…

一个时代的结束:Centos7将在6月30日退出历史舞台

友情提醒&#xff1a; 如果你使用的是曾经辉煌一时的CentOS Linux 7&#xff0c;一直拖延没有迁移&#xff0c;那么现在距离它正式寿终正寝还有不到一周的时间。 CentOS Linux 7 的结束日期仍定在2024年6月30日。红帽早在 2020 年就做出了有争议的举动&#xff0c;将重点转移到…

前后端交互整合 - Wiki

集成 Http 库 axios 首先在前端工程中安装 axios ,切换为 wiki / web 目录下,安装命令行为 npm install axios --save 通过 axios 调用电子书列表接口: 定义 setup( ) 方法,页面被调用时便会执行此方法,在方法中要想使用 axios ,首先需要引入 axios 包: import axios f…

网络安全 文件上传漏洞-18 第十八关 Pass-18

点击进入第十八关&#xff0c;并选择显示代码&#xff1a; //index.php $is_upload false; $msg null; if (isset($_POST[submit])) {require_once("./myupload.php");$imgFileName time();$u new MyUpload($_FILES[upload_file][name], $_FILES[upload_file][tmp…

百日筑基第七天-JAVA开发IDEA调试技巧(常用按钮)

百日筑基第七天-JAVA开发IDEA调试技巧&#xff08;常用按钮&#xff09; 1.Show Execution Point 快捷键&#xff1a;Alt F10 回到当前激活的断点处&#xff1b;当你的鼠标不在断点所处的行&#xff0c;点击之后&#xff0c;会立马复位到断点处&#xff1b; 2.Step Ove 快…

你需要精益管理咨询公司的N+1个理由

近年来&#xff0c;精益管理作为一种被全球众多知名企业验证过的成功管理模式&#xff0c;越来越受到企业的青睐。但是&#xff0c;为何在实施精益管理的过程中&#xff0c;众多企业纷纷选择请咨询公司来协助呢&#xff1f;今天&#xff0c;我们就来一起揭秘这背后的原因。 1. …

go Channel原理 (三)

Channel 设计原理 不要通过共享内存的方式进行通信&#xff0c;而是应该通过通信的方式共享内存。 在主流编程语言中&#xff0c;多个线程传递数据的方式一般都是共享内存。 Go 可以使用共享内存加互斥锁进行通信&#xff0c;同时也提供了一种不同的并发模型&#xff0c;即通…

使用热力图表示联邦学习场景中的客户端数据分布

用于生成热力图&#xff0c;记录过程&#xff0c;方便之后直接使用。 使用场景&#xff1a;联邦学习中显示客户端数据分布&#xff0c;或者显示数据分布的各类其他场景 文章目录 一、代码hot.py使用方法 二、参数解释三、样图关键词 一、代码 写这段代码时主要考虑联邦学习中显…

阿里云物联网应用层开发:第一部分,项目简介

文章目录 1、物联网应用层简介2、阿里云物联网应用层开发例程主要内容3、需要掌握基础知识 1、物联网应用层简介 应用层是物联网系统的用户界面&#xff0c;它提供了用户与系统交互的接口&#xff0c;这一层是将网络传输层的数据结果以易于理解和使用的方式呈现给用户&#xf…

在AvaotaA1全志T527开发板上使用 SSH 连接开发板

使用 SSH 连接开发板 启动系统 前提条件&#xff1a; 确保已经制作好AvaotaA1系统镜像至TF卡。 ​ 确保开发板电源供电正常&#xff1a;默认SPI显示屏有图案输出。 确保当前环境下有可以正常上网的路由器RJ45网线接口。 获取IP地址 如果想通过ssh去登陆开发板系统&#…

广义加性模型

简要介绍 在统计学中&#xff0c;广义加性模型&#xff08;GAM&#xff09;是一种广义线性模型&#xff0c;其中线性响应变量线性地依赖于一些预测变量的未知光滑函数&#xff0c;并且人们对这些光滑函数的推理很感兴趣。 GAM最初由Trevor Hastie和Robert Tibshirani[1]开发&…

数据写入流程,数据读取流程

理解客户端在HDFS上读、写数据的流程 数据写入流程 1. 客户端向NameNode发起请求 2. NameNode审核权限、剩余空间后&#xff0c;满足条件允许写入&#xff0c;并告知客户端写入的DataNode地址 3. 客户端向指定的DataNode发送数据包 4. 被写入数据的DataNode同时完成数据副本的…