目录

47. 力扣203 移除链表元素

47.1 题目解析:

​编辑 

47.2 算法思路:

47.3 代码演示:

​编辑 

48. 力扣2.两数相加

48.1 题目解析:

​编辑 

48.2 算法思路;

48.3 代码演示:

48.4 总结反思:

49. 力扣24 两两交换链表中的节点

49.1 题目解析:

49.2 算法思路:

49.3 代码演示:

​编辑

49.4 总结反思

50. 力扣 143 重排链表

50.1 题目解析:

50.2 算法思路:

 

50.3 代码演示:

​编辑

50.4 总结反思

51 力扣 25 k个1组翻转链表

51.1题目解析:

51.2 算法思路:

​编辑 

 

51.3 代码演示:

 ​编辑

51.4 总结反思


今天咱们讲链表,以及链表常用操作总结:

就是咱们接下来的题目,基本上都会用到这几种方法:

1.画图!!!(这个画图特别重要),因为画图直观加形象加便于我们理解

2.咱们在做题的时候,可以引入虚拟节点:

【1】虚拟头结点可以简化边界的处理。

【2】可以统一操作,不需要再单独的对头节点进行特殊操作。为什么这么说呢?是因为

那么接下来咱们通过一道题目来进行实际的探查:

47. 力扣203 移除链表元素

47.1 题目解析:

 

题目很简单。就是删除值为val的某个节点

47.2 算法思路:

就是遍历整个链表,找到值为val的那个元素,然后再跳过这个元素即可。

47.3 代码演示:

 

//不带虚拟头结点
class Solution {
public:ListNode* removeElements(ListNode* head, int val) {// 需要单独处理头节点,确保头结点不为空,并且里面的数值也不可以是空,头结点是个有效的头结点while (head != nullptr && head->val == val) {head = head->next;}// 再处理其他节点ListNode* cur = head;while (cur != nullptr && cur->next != nullptr) {if (cur->next->val == val) {cur->next = cur->next->next;}else {cur = cur->next;}}return head;}
};

这个是不带虚拟头结点的版本,可以看出来,咱们需要单独的处理一下头结点,然后再是处理其他的节点。并且,如果这样的话,步骤就比较繁琐,而且不容易统一处理方式

//带虚拟头结点的版本
class Solution {
public:ListNode* removeElements(ListNode* head, int val) {ListNode* dummy = new ListNode(0); // 虚拟头节点指向headdummy->next = head;ListNode* cur = dummy;while (cur->next != nullptr) {if (cur->next->val == val) {cur->next = cur->next->next; // 统一删除逻辑,直接跳过这个节点即可,也是单向链表通常的删除逻辑}else {cur = cur->next;}}return dummy->next; // 新头节点}
};

这个是带虚拟头结点的版本,是不是很爽,基本可以统一处理节点了(包括头节点)。

所以,虚拟头节点的好处就是:

1. 避免头节点的特殊处理

  • 问题:在链表操作中,如果直接操作头节点(如删除、反转、插入等),通常需要额外判断:

    • 如果删除头节点,需要重新指定 head

    • 如果插入新节点到头部,需要更新 head

  • 虚拟头结点的作用

    • 提供一个统一的“前驱节点”,使得头节点和其他节点的操作逻辑一致。

    • 无论原头节点如何变化,dummy->next 始终指向新头节点。

2. 简化代码逻辑

  • 没有虚拟头结点:需要单独处理头节点,代码分支增多。

  • 有虚拟头结点:所有节点(包括原头节点)都有前驱节点,可以用统一逻辑处理。

 

在C++中,虚拟头节点一般这样创建:

ListNode* dummy = new ListNode(0); // 值任意,一般用0
dummy->next = head; // 指向原头节点
// ... 操作链表
return dummy->next; // 返回新头节点

3.不要吝啬空间,该创建的时候就得创建,这样可以省去不少的麻烦:

想必大家多多少少都做过这种题目吧,就是断开两个节点之间的链接,让第三个节点链接上去。那么此时,咱们如果不定义一个next节点变量,直接就有两个变量(prev,cur)去连接的话,此时就得注意一下顺序了。只能按照咱们图中所指的顺序来进行链接。

prev->next->prev=cur;
cur->next=prev->next;
prev->next=cur;
cur->prev=prev;

如果前两行与后两行颠倒了,先执行后两行,那么这样的话,会发现找不到后面那个节点了,也会导致cur->next=cur,自己指向自己,死循环。所以,这个时候就体现了,再定义一个新变量的必要性。再定义一个新变量,就不需要再担心执行的顺序了,管你先执行那个,都可以。

4.快慢指针

一般用于链表中环的判断,找到链表中环的入口,找到链表中倒数第n个节点。

而咱们链表的常用操作就是1.创建一个新的节点,2.尾插 3.头插(这个头插法在逆序链表中还是比较常用的)

这个是尾插,还是比较简单的。

这个是头插。相对于尾插来说,还是复杂一点的。

48. 力扣2.两数相加

48.1 题目解析:

 

这个题目有个关键的地方就是逆序操作,咱们可以直接从头开始加(因为这个是逆序存储的),即2,4,3 逆序存储为 3,4,2。5,6,4 逆序存储为4,6,5 。那么3,4,2与4,6,5相加,由于是从最低位开始加,那么逆序之后,对应的就从链表的头开始相加,非常的方便。

48.2 算法思路;

直接模拟两数相加即可

 

以上就是这个题目的所有的算法思路。

48.3 代码演示:

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* addTwoNumbers(ListNode* l1, ListNode* l2) {ListNode* cur1 = l1; ListNode* cur2 = l2;ListNode* newnode = new ListNode(0);//创建一个虚拟头结点,记录最终结果int t = 0;ListNode* prev = newnode;while (cur1 || cur2 || t){if (cur1){t += cur1->val;cur1 = cur1->next;}if (cur2){t += cur2->val;cur2 = cur2->next;}prev->next = new ListNode(t % 10);//新建一个节点才能去存储数字,因为此时newnode后面并没有节点。prev = prev->next;t /= 10;}ListNode* next = newnode->next;delete newnode;return next;}
};

48.4 总结反思:

这道题目充分的利用了上面的知识,所以还得具备充分的知识储备才可以。

49. 力扣24 两两交换链表中的节点

49.1 题目解析:

本题交换节点需要注意的是,不可以只交换节点里面的数值,必须得连着节点一起交换才可以。

49.2 算法思路:

OK,算法思路就是上面我写的这些,大家好好的参悟一下

49.3 代码演示:

class Solution {
public:ListNode* swapPairs(ListNode* head) {if (head == nullptr || head->next == nullptr) return head;//若是链表为空,或者是链表只有一个元素,直接返回。注意,这里判断的标准时head,因为head才是真正的头结点,而不是newHead。判断这个的目的是为了预防下面的cur->next(cur为空,cur就是头结点,链表为空)与next->next(链表只有一个元素,此时next为空),空指针解引用ListNode* newHead = new ListNode(0);newHead->next = head;//head才是真正的头结点ListNode* prev = newHead;ListNode* cur = prev->next; ListNode* next = cur->next; ListNode* nnext = next->next;while (cur && next){//更新节点prev->next = next;next->next = cur;cur->next = nnext;//交换指针prev = cur;cur = nnext;if (cur) next = cur->next;if (next) nnext = next->next;}ListNode* kk = newHead->next;delete newHead;return kk;}
};

49.4 总结反思

其实这道题目也是用到了上面咱们所说的这些算法,大家还是得好好的参悟一下

50. 力扣 143 重排链表

50.1 题目解析:

这个题目其实是让你按照一定得顺序重新排一下链表

 

那么其实题目就是这样的,所以咱们可以分为三个步骤

1.找到链表的中间节点

使用快慢指针(一定要画图,考虑使用slow得落点在哪里)

2.把后面的部分逆序

使用双指针(或者三指针),或者头插法(推荐)完成逆序

3.合并两个有序链表,(双指针),按照一定得顺序合并

50.2 算法思路:

 关于slow的落点,咱们直接砍掉slow后面的部分给逆序即可。不过需要引入虚拟头结点。一开始得先头插进第二个链表:

 

 

 

 

50.3 代码演示:

class Solution {
public:void reorderList(ListNode* head) {if (head == nullptr || head->next == nullptr || head->next->next == nullptr) return;//先找到中间节点ListNode* slow = head; ListNode* fast = head;while (fast && fast->next)//注意循环条件是这个,不是slow<fast{slow = slow->next;fast = fast->next->next;}//之后使用头插法对slow后面的部分进行逆序ListNode* head2 = new ListNode(0);ListNode* cur = slow->next;slow->next = nullptr;while (cur){ListNode* next = cur->next;//再定义一个变量存储这个cur,就是为了防止顺序问题cur->next = head2->next;head2->next = cur;cur = next;}//之后进行合并链表ListNode* kk = new ListNode(0);ListNode* prev = kk;ListNode* cur1 = head;ListNode* cur2 = head2->next;while (cur1){//先合并第一个链表prev->next = cur1;cur1 = cur1->next;prev = prev->next;//再合并第二个链表if (cur2){prev->next = cur2;prev = prev->next;cur2 = cur2->next;}}delete head2;delete kk;}
};

这样的代码量,放在整个链表对应的题目里面,算是比较多的了

50.4 总结反思

重点的东西还是开篇所讲的那些东西,还是希望大家要好好的研读,最好记住。

51 力扣 25 k个1组翻转链表

51.1题目解析:

其实这一题就考了一个模拟,并且也用到了逆序

51.2 算法思路:

 

 

 

51.3 代码演示:

 

class Solution {
public:ListNode* reverseKGroup(ListNode* head, int k) {//求出需要逆序多少组int n = 0;ListNode* cur = head;while (cur){cur = cur->next;n++;}n /= k;//重复n次,将长度为k的链表逆序即可ListNode* newHead = new ListNode(0);ListNode* prev = newHead;ListNode* cur1 = head;for (int i = 0; i < n; i++){ListNode* tmp = cur1;//把一开始的cur1的位置给存起来,由于是逆序存储,所以说后面的tmp的位置,就是下一组逆序头插的位置for (int j = 0; j < k; j++)//不可写成whilw(k--)/*k 被修改后没有恢复:内层 while (k--) 会直接修改 k 的值,导致后续循环(如果有)的 k 不正确。例如:第一次循环 k = 3,执行完 while (k--) 后 k = -1。如果还有第二次循环,k 已经变成 - 1,导致内层循环不执行,逻辑错误。n 被修改后不影响逻辑,但 k 被修改会导致后续组的反转次数错误。*/{ListNode* next = cur1->next;cur1->next = prev->next;prev->next = cur1;cur1 = next;}prev = tmp;}//剩下的直接接在后面即可prev->next = cur1;ListNode* cur2 = newHead->next;delete newHead;return cur2;}
};

这个地方,作者一开始写的时候,出现了一个失误,给大家看一下:

这个是错误的:
while(n--)  // 直接修改n的值
{ListNode* tmp = cur1;while(k--)  // 直接修改k的值{// 反转逻辑}prev = tmp;
}

问题:

  1. k 被修改后没有恢复:内层 while(k--) 会直接修改 k 的值,导致后续循环(如果有)的 k 不正确。例如:

    • 第一次循环 k=3,执行完 while(k--) 后 k=-1

    • 如果还有第二次循环,k 已经变成 -1,导致内层循环不执行,逻辑错误。

  2. n 被修改后不影响逻辑,但 k 被修改会导致后续组的反转次数错误。

这个是正确的:
for(int i=0; i<n; i++)  // 不修改n
{ListNode* tmp = cur1;for(int j=0; j<k; j++)  // 不修改k{// 反转逻辑}prev = tmp;
}

改进点:

  1. 使用 for 循环代替 while 循环

    • for(int j=0; j<k; j++) 不会修改 k 的值,确保每次反转都是 k 个节点。

    • for(int i=0; i<n; i++) 也不会修改 n,但即使修改 n 也不会影响逻辑。

  2. 避免了 k 被错误修改

    • 由于 k 保持不变,每次都能正确反转 k 个节点。

 

51.4 总结反思

今天的知识基本都用到了我在开篇所讲的那些知识

本篇完................. 

 

 

 

 

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nl2sql grpo强化学习训练,加大数据量和轮数后,准确率没提升,反而下降了,如何调整

在NL2SQL任务中使用GRPO强化学习训练时&#xff0c;增加数据量和训练轮数后准确率下降&#xff0c;通常是由过拟合、训练不稳定、奖励函数设计不合理、数据质量问题或探索-利用失衡等原因导致的。以下是具体的诊断思路和调整策略&#xff0c;帮助定位问题并优化性能&#xff1a…

PHP/Java/Python实现:如何有效防止恶意文件上传

文章目录 木马病毒防范:文件上传如何彻底防止伪造文件类型 引言 一、文件类型伪造的原理与危害 1.1 常见伪造手段 1.2 潜在危害 二、防御体系设计 2.1 防御架构 三、核心防御技术实现 3.1 服务端验证实现 3.1.1 文件内容检测(Python示例) 3.1.2 扩展名与内容双重验证(Java示…

SpringBoot系列之基于Redis的分布式限流器

SpringBoot系列之基于Redis的分布式限流器 SpringBoot 系列之基于 Redis 的分布式限流器 图文并茂,代码即拷即用,支持 4 种算法(固定窗口 / 滑动窗口 / 令牌桶 / 漏桶) 一、为什么要用分布式限流? 单机 Guava-RateLimiter 在集群下会 各玩各的,流量漂移,无法全局控量。…

面试遇到的问题2

Redisson的看门狗相关问题 首先要明确一点&#xff0c;看门狗机制的使用方式是&#xff1a;在加锁的时候不加任何参数&#xff0c;也就是&#xff1a; RLock lock redisson.getLock("myLock"); try {lock.lock(); // 阻塞式加锁// 业务逻辑... } finally {lock.unl…

Linux—进程概念与理解

目录 1.冯诺依曼体系结构 小结&#xff1a; 2.操作系统 概念&#xff1a; 结构示意图&#xff1a; 理解操作系统&#xff1a; 用户使用底层硬件层次图&#xff1a;​编辑 3.进程 概念 结构示意图 task_ struct内容分类 典型用法示例 观察进程: 了解 PID PPID 查…

LeetCode 面试经典 150_数组/字符串_买卖股票的最佳时机(7_121_C++_简单)(贪心)

LeetCode 面试经典 150_数组/字符串_买卖股票的最佳时机&#xff08;7_121_C_简单&#xff09;题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;贪心算法&#xff09;&#xff1a;代码实现代码实现&#xff08;思路一&…

Ubuntu 18.04 repo sync报错:line 0: Bad configuration option: setenv

repo sync时报 line 0: Bad configuration option: setenv因为 Ubuntu 18.04 默认的 openssh-client 是 7.6p1&#xff0c;还不支持 setenv&#xff0c;但是.repo/repo/ssh.py 脚本中明确地传入了 SetEnv 参数给 ssh&#xff0c;而你的 OpenSSH 7.6 不支持这个参数。需要按如下…

bug记录-stylelint

BUG1不支持Vue文件内联style样式解决&#xff1a; "no-invalid-position-declaration": null

前端开发(HTML,CSS,VUE,JS)从入门到精通!第一天(HTML5)

一、HTML5 简介1&#xff0e;HTML全称是 Hyber Text Markup Language&#xff0c;超文本标记语言&#xff0c;它是互联网上应用最广泛的标记语言&#xff0c;简单说&#xff0c;HTML 页面就等于“普通文本HTML标记&#xff08;HTML标签&#xff09;“。2&#xff0e;HTML 总共经…

智慧收银系统开发进销存:便利店、水果店、建材与家居行业的—仙盟创梦IDE

在数字化转型的浪潮中&#xff0c;收银系统已不再局限于简单的收款功能&#xff0c;而是成为企业进销存管理的核心枢纽。从便利店的快消品管理到建材家居行业的大宗商品调度&#xff0c;现代收银系统通过智能化技术重塑了传统商业模式。本文将深入探讨收银系统在不同行业进销存…

三维扫描相机:工业自动化的智慧之眼——迁移科技赋能智能制造新纪元

在当今工业4.0时代&#xff0c;自动化技术正重塑生产流程&#xff0c;而核心工具如三维扫描相机已成为关键驱动力。作为工业自动化领域的“智慧之眼”&#xff0c;三维扫描相机通过高精度三维重建能力&#xff0c;解决了传统制造中的效率瓶颈和精度痛点。迁移科技&#xff0c;自…

Jmeter的元件使用介绍:(九)监听器详解

监听器主要是用来监听脚本执行的取样器结果。Jmeter的默认监听器有&#xff1a;查看结果树、聚合报告、汇总报告、用表格查看结果&#xff0c;断言结果、图形结果、Beanshell监听器、JSR223监听器、比较断言可视化器、后端监听器、邮件观察器&#xff0c;本文介绍最常用的监听器…

联通元景万悟 开源,抢先体验!!!

简介&#xff1a; 元景万悟智能体平台是一款面向企业级场景的一站式、商用license友好的智能体开发平台&#xff0c;是业界第一款go语言&#xff08;后端&#xff09;开发的智能体开发平台&#xff08;7月19日&#xff09;&#xff0c;coze studio开源是7月26日&#xff0c;同时…

Git之本地仓库管理

一.什么是Git在学习工作中&#xff0c;我们经常会遇到改文档的场景。一个文档可能会被我们修改多次&#xff0c;而最终真正使用的可能是最先的几版。而如果我们直接在原文档上修改&#xff0c;就会导致无法找到最先的几次。这也就导致我们要对我们所有的版本进行维护&#xff0…

Go再进阶:结构体、接口与面向对象编程

&#x1f680; Go再进阶&#xff1a;结构体、接口与面向对象编程 大家好&#xff01;在前两篇文章中&#xff0c;我们深入学习了Go语言的流程控制语句以及数组和切片的使用并且还对Go 语言的核心知识点进行了补充讲解&#xff0c;这些知识让我们能够编写出更为复杂和灵活的程序…

Python入门第六课:现代开发与前沿技术

异步编程(asyncio) 1. 协程基础 import asyncio import time# 定义协程函数 async def say_after(delay, message):await asyncio.sleep(delay)print(message)# 主协程 async def main():print(f"开始时间: {time.strftime(%X)}")# 顺序执行await say_after(2, 你…

STM32移植LVGL9.2.1教程

一、环境说明 &#xff08;1&#xff09;开发板&#xff1a;STM32F401RCT6核心板&#xff08;网上很多&#xff0c;价格只有几块钱&#xff09; &#xff08;2&#xff09;屏幕&#xff1a;2.8寸spi屏gt911触摸 转接板&#xff08;某宝有卖&#xff0c;没有推广自行搜索&…

python学智能算法(二十九)|SVM-拉格朗日函数求解中-KKT条件理解

【1】引言 前序学习阶段中&#xff0c;我们掌握了最佳分割超平面对应的构造拉格朗日函数极值为&#xff1a; L(w,b,α)∑i1mαi−12∑i,j1mαiαjyiyjxiTxjL(w,b,\alpha)\sum_{i1}^{m}\alpha_{i}-\frac{1}{2}\sum_{i,j1}^{m}\alpha_{i}\alpha_{j}y_{i}y_{j}x_{i}^{T}x_{j}L(w,…

大模型应用开发1-认识大模型

1.基础概念 1.1 AI的概念&#xff1a; AI&#xff0c;⼈⼯智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;使机器能够像⼈类⼀样思考、学习和解决问题的技术。AI发展⾄今⼤概可以分为三个阶段&#xff1a;其中&#xff0c;深度学习领域的自然语言处理&#…

Linux 远程连接解析:SSH 协议理论与应用

Linux 远程连接解析&#xff1a;SSH 协议理论与应用在网络互联的时代&#xff0c;远程管理服务器已成为常态。SSH&#xff08;Secure Shell&#xff09;作为一种安全的网络协议&#xff0c;凭借其加密机制和灵活的功能&#xff0c;成为 Linux 系统远程操作的事实标准。本文将从…