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

news2024/11/24 2:58:59

目录

删除元素的万能方法

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

一、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;
    }
}

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

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

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

相关文章

【UML用户指南】-23-对高级行为建模-状态机

目录 1、概述 2、状态 2.1、状态的组成 3、转移 3.1、转移的组成 4、高级状态和转移 4.1、进入效应和退出效应 4.2、内部转移 4.3、do活动 4.4、延迟事件 4.5、子状态机 5、子状态 5.1、非正交子状态 5.2、历史状态 5.3、正交子状态 6、分叉与汇合 7、主动对象…

【摄像头标定】双目摄像头标定及矫正-opencv(python)

双目摄像头标定及矫正 棋盘格标定板标定矫正 棋盘格标定板 本文使用棋盘格标定板&#xff0c;可以到这篇博客中下载&#xff1a;https://blog.csdn.net/qq_39330520/article/details/107864568 标定 要进行标定首先需要双目拍的棋盘格图片&#xff0c;20张左右&#xff0c;…

【最简单】解决windows安装wsl,出现WslRegisterDistribution failed with error: 0x8007019e的问题

从官网下载安装包安装ubuntu18.04的过程中出现了下面的错误 在Windows上安装Windows Subsystem for Linux (WSL) 时&#xff0c;可能会遇到以下错误&#xff1a; WslRegisterDistribution failed with error: 0x8007019e 这个错误通常是由于系统未启用必要的功能或未正确配置…

计算机网络微课堂(湖科大教书匠)TCP部分

计算机网络微课堂&#xff08;湖科大教书匠&#xff09;TCP部分 【计算机网络微课堂&#xff08;有字幕无背景音乐版&#xff09;】 TCP的流量控制 一般来说&#xff0c;我们希望数据传输得更快一些。但如果发送方把数据发送得过快&#xff0c;接收方就可能来不及接收&#…

【Vue】Vue3基础

VUE3基础 1、简介2、创建工程2.1 基于vue-cli创建&#xff08;脚手架webpack&#xff09;2.2 基于vite创建&#xff08;推荐&#xff09;2.3 目录结构2.4 vscode插件推荐 3、核心语法3.1 选项式&#xff08;options API&#xff09;和组合式&#xff08;composition API&#x…

json文件 增删查改

默认收藏夹 qt操作json格式文件... 这个人的 写的很好 我的demo全是抄他的 抄了就能用 —————————— 下次有空把我的demo 传上来 在E盘的demo文件夹 json什么名字

「ETL趋势」FDL数据开发支持版本管理、实时管道支持多对一、数据源新增支持神通

FineDataLink作为一款市场上的顶尖ETL工具&#xff0c;集实时数据同步、ELT/ETL数据处理、数据服务和系统管理于一体的数据集成工具&#xff0c;进行了新的维护迭代。本文把FDL4.1.8最新功能作了介绍&#xff0c;方便大家对比&#xff1a;&#xff08;产品更新详情&#xff1a;…

Profinet IO从站数据 转EtherCAT项目案例

这里是引用 目录 1 案例说明 1 2 VFBOX网关工作原理 1 3 准备工作 2 4 使用PRONETA软件获取PROFINET IO从站的配置信息 2 5 设置网关采集PROFINETIO从站设备数据 5 6 启动ETHERCAT从站转发采集的数据 8 7 选择槽号和数据地址 9 8 选择子槽号 11 9 案例总结 12 1 案例说明 设置…

《昇思25天学习打卡营第12天 | 昇思MindSpore基于MindSpore的GPT2文本摘要》

12天 本节学习了基于MindSpore的GPT2文本摘要。 1.数据集加载与处理 1.1.数据集加载 1.2.数据预处理 2.模型构建 2.1构建GPT2ForSummarization模型 2.2动态学习率 3.模型训练 4.模型推理

揭秘Etched AI:三个哈佛辍学00后挑战英伟达,推出Transformer专用ASIC芯片sohu

人工智能领域最近掀起了一股新的热潮&#xff0c;三位哈佛辍学的00后本科生创建了Etched AI&#xff0c;并成功推出了一款超强AI芯片sohu&#xff0c;直指英伟达的AI芯片帝国。这款芯片被誉为比英伟达H100快20倍&#xff0c;吸引了众多科技界的关注。本文将深入探讨Etched AI及…

五、Spring IoCDI ★ ✔

5. Spring IoC&DI 1. IoC & DI ⼊⻔1.1 Spring 是什么&#xff1f;★ &#xff08;Spring 是包含了众多⼯具⽅法的 IoC 容器&#xff09;1.1.1 什么是容器&#xff1f;1.1.2 什么是 IoC&#xff1f;★ &#xff08;IoC: Inversion of Control (控制反转)&#xff09;总…

带上作弊器,我不得起飞

前言 过去,我们对人工智能既期待又害怕.人类的惰性希望人工智能可以帮助大家从大部分繁重的工作中解放出来,但又害怕它失控. 智能系统的好处 工作方面 自动化与效率提升&#xff1a;可以自动执行许多重复性和低技能的任务&#xff0c;如制造业中的装配、数据输入和办公室的客户…

java的序列化和反序列化

一、概念 序列化是将对象的常态存储到特定的存储介质中的过程。 反序列化是将特定的存储介质中的数据重新构建对象的过程。 问题 为每个对象属性——编写读写代码&#xff0c;过程很繁琐且非常容易出错&#xff0c;如何解决&#xff1f; 二、使用Object Output Stream类实现…

敏捷开发笔记(第9章节)--开放-封闭原则(OCP)

目录 1&#xff1a;PDF上传链接 9.1 开放-封闭原则&#xff08;OCP&#xff09; 9.2 描述 9.3 关键是抽象 9.3.1 shape应用程序 9.3.2 违反OCP 糟糕的设计 9.3.3 遵循OCP 9.3.4 是的&#xff0c;我说谎了 9.3.5 预测变化和“贴切的”结构 9.3.6 放置吊钩 1.只受一次…

Spring专题一:源码编译

下载源码 因为公司使用的是Spring5.2.x所以就下载了这个版本&#xff0c;github源码地址如下&#xff1a; GitHub - spring-projects/spring-framework at v5.2.6.RELEASE&#xff1a; 如果网络不稳定可以使用下载压缩版即可&#xff0c;网络稳定的话还是建议使用git clone …

C语言的数据结构:树与二叉树(哈夫曼树篇)

前言 上篇讲完了二叉树&#xff0c;二叉树的查找性能要比树好很多&#xff0c;如平衡二叉树保证左右两边节点层级相差不会大于1&#xff0c;其查找的时间复杂度仅为 l o g 2 n log_2n log2​n&#xff0c;在两边层级相同时&#xff0c;其查找速度接近于二分查找。1w条数据&am…

今日AI提示词|新媒体运营场景-小红薯笔记通用指令

指令写作的技巧与步骤 明确定义需求 提供足够的上下文 使用简单直白的语言 举例说明 添加限制条件 多次迭代优化&#xff0c;给出详细的要求 小红薯笔记通用指令 从现在开始&#xff0c;你担任我的小红书创作者。你的任务是根据我提供给你的主体&#xff0c;撰写一篇小…

运维锅总详解HAProxy

本文尝试从HAProxy简介、HAProxy工作流程及其与Nginx的对比对其进行详细分析&#xff1b;在本文最后&#xff0c;给出了为什么Nginx比HAProxy更受欢迎的原因。希望对您有所帮助&#xff01; HAProxy简介 HAProxy&#xff08;High Availability Proxy&#xff09;是一款广泛使…

开源项目-商城管理系统

哈喽,大家好,今天主要给大家带来一个开源项目-商城管理系统 商城管理系统分前后端两部分。前端主要有商品展示,我的订单,个人中心等内容;后端的主要功能包括产品管理,门店管理,会员管理,订单管理等模块 移动端页面

汽车电子工程师入门系列——AUTOSAR通信服务框架(上)

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…