移除链表元素(最优解)

news/2024/12/22 20:27:28 标签: 算法, leetcode

题目来源

203. 移除链表元素 - 力扣(LeetCode)


题目描述

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

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

提示:

  • 列表中的节点数目在范围 [0, 104] 内
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

题目限制

题目用最优解实现求解


思路分析

一、整体思路概述

解决这个问题的核心思路是遍历整个链表,在遍历过程中判断每个节点的值是否等于给定的 val,如果相等,则将该节点从链表中删除,最终返回处理后的链表头节点。不过,在实际操作过程中,为了便于统一处理逻辑,尤其是应对要删除的节点可能是链表头节点这种特殊情况,我们通常会采用添加 “虚拟头节点” 的技巧来简化代码实现。

二、具体步骤与思路解析

(一)处理边界情况(空链表)

首先需要考虑一种特殊的边界情况,那就是当传入的链表本身就是空链表时(即 head 为 nullptr),这种情况下没有节点可供删除操作,直接返回原链表的头节点 head 就可以了,因为空链表无需做任何修改,保持原样返回就是正确的处理方式。

(二)添加虚拟头节点

为了让链表的删除操作逻辑更加简洁统一,我们创建一个新的节点作为虚拟头节点,例如:ListNode* dummy = new ListNode(-1);(这里给虚拟头节点赋的值 -1 只是一个示例,具体值不影响操作逻辑,只要便于区分就行),然后让这个虚拟头节点的 next 指针指向原来链表的头节点,即 dummy->next = head;

此时,我们后续的操作就可以从这个虚拟头节点开始遍历链表了,这么做的好处在于,当需要删除的节点恰好是原链表的头节点时,我们无需单独写额外的逻辑去处理头节点的删除情况,而是可以和删除其他普通节点一样,按照统一的逻辑进行处理,大大简化了代码的复杂度和逻辑判断。

(三)遍历链表并删除指定值的节点

接下来就是通过循环来遍历整个链表,以实现对每个节点值的检查以及相应的删除操作。常用的做法是使用一个指针(设为 cur),初始时让它指向我们创建的虚拟头节点,然后通过 while(cur->next) 这样的循环条件来进行循环,只要 cur 指针指向的当前节点的下一个节点存在(即 cur->next 不为 nullptr),就继续循环去检查后续的节点。

在循环体内部,我们需要对当前节点(也就是 cur 指针指向的节点)的下一个节点(通过 cur->next 访问)的值进行判断。如果发现 cur->next->val == val,这就意味着找到了一个需要删除的节点,此时我们通过改变指针的指向来实现节点删除,具体操作就是让 cur 指针的 next 指针跳过这个要删除的节点,直接指向它的下下个节点,即 cur->next = cur->next->next;,这样就相当于把值为 val 的这个节点从链表中移除掉了,实际是改变了链表的内部指针连接关系,使得该节点不再属于链表的一部分。

如果当前节点的下一个节点的值不等于 val,那就说明这个节点不需要被删除,此时我们只需要让 cur 指针往后移动一位,即 cur = cur->next;,继续去检查下一个节点的情况就可以了。

(四)返回处理后的链表头节点

经过前面的遍历和节点删除操作后,最后我们需要返回处理后的链表的头节点。由于之前添加了虚拟头节点,所以真正的链表头节点现在是由虚拟头节点的 next 指针指向的,因此我们直接返回 dummy->next 就可以得到经过处理后的链表的正确头节点了,这个头节点指向的链表就是已经删除了所有值为 val 的节点后的新链表。

三、总结

通过以上步骤,我们利用添加虚拟头节点的技巧,配合循环遍历和指针操作,统一且有条理地实现了删除链表中所有满足特定值节点的功能。这种思路在处理链表相关问题,尤其是涉及节点删除、插入等操作时经常会用到,希望大家通过对这道题思路的详细分析,能够更好地掌握链表操作的技巧,灵活应对类似的编程挑战呀


具体代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if(!head)return head;
        ListNode* cur=new ListNode(-1);
        cur->next=head;
        ListNode* pre=cur;
        while(cur->next)
        {
            if(cur->next->val==val)cur->next=cur->next->next;
            else cur=cur->next;
        }return pre->next;
    }
};

在单链表操作中,常常会遇到依据特定值移除链表中节点的需求。下面这段 C++ 代码实现了给定一个单链表头节点指针 head 以及一个整数 val,将链表中所有值为 val 的节点移除的功能。

首先是边界情况处理,if(!head) return head; 这句代码考虑到了传入的链表为空(即 head 为 nullptr)的情况,此时无需操作,直接返回原 head 即可。

接着创建了一个虚拟头节点 ListNode* cur = new ListNode(-1);,并让其 next 指针指向真正的链表头节点 cur->next = head;,同时定义 ListNode* pre = cur;,这里的虚拟头节点作用很大,能统一后续移除节点的逻辑,避免对头节点单独判断。

核心的移除节点操作在 while(cur->next) 循环里,只要当前节点(cur 指向)的下一个节点存在就继续循环。循环体中通过 if - else 判断,若 cur->next->val == val,执行 cur->next = cur->next->next,即跳过值为 val 的节点,改变链表指针连接实现 “删除”;若不符合条件,则执行 cur = cur->next,继续检查下一个节点。

最后通过 return pre->next; 返回经过处理后的链表头节点,因为添加了虚拟头节点,真正的链表头就在 pre 的 next 指针指向处。总之,这段代码借助虚拟头节点,以清晰的逻辑和简洁的方式实现了单链表中指定值节点的移除功能,实用性很强哦。


http://www.niftyadmin.cn/n/5795812.html

相关文章

【杂谈】虚拟机与EasyConnect运行巧设:Reqable助力指定应用流量专属化

场景 公司用的是EasyConnect&#xff0c;这个软件非常好用&#xff0c;也非常稳定&#xff0c;但是有个缺点&#xff0c;就是会无条件拦截本机所有流量&#xff0c;而且会加入所有运行的exe程序&#xff0c;实现流量全部走代理。 准备材料 一个windows/Linux 桌面版虚拟机Ea…

Webpack学习笔记(5)

1.拆分开发环境和生产环境配置 很多配置在开发环境和生产环境存在不一致的情况&#xff0c;比如开发环境没有必要设置缓存&#xff0c;生产环境需要设置公共路径等等。 2.公共路径 使用publicPath配置项&#xff0c;可以通过它指定应用程序中所有资源的基础路径。 webpack.…

Eureka服务注册源码

spring-cloud-starter-netflix-eureka-client 版本是3.0.3 核心装备类&#xff1a; EurekaClientAutoConfiguration EurekaDiscoveryClientConfiguration 核心类&#xff0c;以及引用的关系如下 EurekaRegistration - EurekaInstanceConfigBean 实例配置bean- ApplicationInfo…

JaxaFx学习(三)

目录&#xff1a; &#xff08;1&#xff09;JavaFx MVVM架构实现 &#xff08;2&#xff09;javaFX知识点 &#xff08;3&#xff09;JavaFx的MVC架构 &#xff08;4&#xff09;JavaFx事件处理机制 &#xff08;5&#xff09;多窗体编程 &#xff08;6&#xff09;数据…

element-puls封装表单验证

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 在做项目中会有一些简单的表单非空验证&#xff0c;这些验证比较简单&#xff0c;就是代码看着有点多&#xff0c;做起来浪费时间&#xff0c;所以我们可以将这个方法封装起来&#xff0c;然后挂载全…

【从零开始入门unity游戏开发之——C#篇23】C#面向对象继承——`as`类型转化和`is`类型检查、向上转型和向下转型、里氏替换原则(LSP)

文章目录 一、as类型转化和is类型检查1、as 关键字使用场景&#xff1a;语法&#xff1a;示例&#xff1a;特点&#xff1a; 2、is 关键字使用场景&#xff1a;语法&#xff1a;示例&#xff1a;特点&#xff1a; 3、总结 二、向上转型和向下转型1、向上转型示例&#xff1a; 2…

ARM学习(38)多进程多线程之间的通信方式

ARM学习(38)ARM学习(38)多进程多线程之间的通信方式 一、问题背景 笔者在调试模拟器的时候,碰到进程间通信的问题,一个进程在等另外一个进程ready的时候,迟迟等不到,然后通过调试发现,另外一个进程变量已经变化了,但是当前进程变量没变化,需要了解进程间通信的方式…

Android -- 双屏异显之方法一

Android – 双屏异显之方法一&#xff1a;MediaRouter PS: 1. 部分主板可能不支持&#xff0c;得验证&#xff1b; 2. 副屏输出可以不用连接显示屏也能正常后台运行&#xff1b; 3. 主屏Activity内控制副屏&#xff1b; 4. 副屏截图命令&#xff1a;screencap -p -d 1 <pat…