代码随想录4——链表: 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题02.07链表相交、142.环形链表II

news/2024/4/30 4:22:35/文章来源:https://blog.csdn.net/qq_42731705/article/details/127023891

文章目录

  • 1.24两两交换链表中的节点
    • 1.1.题目
    • 1.2.思路
    • 1.3.代码实现
      • 1.3.1.对`next`指针的理解
      • 1.3.2.编程中要备份哪些节点的指针?
      • 1.3.3.代码实现
  • 2.19删除链表的倒数第N个节点
    • 2.1.题目
    • 2.2.思路
  • 3.面试题02.07链表相交
    • 3.1.题目
    • 3.2.解答
  • 4.142.环形链表II
    • 4.1.题目
    • 4.2.思路

1.24两两交换链表中的节点

参考:代码随想录24两两交换链表中的节点

1.1.题目

在这里插入图片描述

1.2.思路

在这里插入图片描述在这里插入图片描述

实际上,仔细看上面的图之后还是得到了链表原来那个非常重要的性质:即操作当前节点,一定要通过上一个节点进行访问,因为需要修改上一个节点的next指针新的指向。因此本题虽然是交换相邻的两个节点BC,但是始终会从B的上一个节点A开始操作,即虽然是只交换两个节点,但是操作的时候始终是在操作3个节点,就是因为对链表结构进行修改之后,需要更新其上一个节点的next指针,因此一直需要从A节点开始操作。

比如上面的最后一个图,当交换完1和2之后,cur指向了1的位置,但是实际上下面我们是要交换3和4,此时再次使用原来的套路,1指向4,4指向3,3再指向4后面的那个元素,可见此时仍然是成立的。

1.3.代码实现

1.3.1.对next指针的理解

今天发现在代码编程中对next理解的还是不清晰,有的时候把他认为是当前节点的next指针变量,有的时候又把它理解成当前节点的下一个节点。显然这两种理解都是正确的,但是不同的场景下需要使用切换不同的理解方式,显然很麻烦。比如cur->next = tmp,这个就把next理解成当前节点的指针变量;如果是cur->next->val = 1,这又要把next理解成当前节点的下一个节点。这样切换很难受。

实际上,只需要把next理解成从节点出来的、指向下一个节点的箭头即可。这样理解永远不会出错,而不用区分他到底是指针变量还是指向的下一个节点。所以后面统一记为next箭头

更新:评论中有人对next指针进行了评价,这促使我对next指针又得到了新的理解。

上面自己说对next指针有两种理解,实际上本质原因就在于next指针是归属于两个节点的,它是链接两个节点的桥梁。对于前一个节点来说,next指针就是它的成员变量,此时它就是一个指针变量,比如cur->next = tmp就是在修改这个变量的值。而对于后一个节点来说,上一个节点的next指向的就是当前这个节点的内存地址,所以如果是cur->next->val = 1,那么此时上一个节点的next就相当于是当前节点,此时next就是指针指向的节点。因此是next指针对前后两个节点的联系,导致了它可以有两种不同的理解。

如果统一把next理解成从当前节点出来的、指向下一个节点的箭头:对于cur->next = tmp,就是修改当前节点的箭头指向的方向,让它指向tmp节点; 对于cur->next->val = 1,就是在访问当前节点的箭头指向的节点的值。

1.3.2.编程中要备份哪些节点的指针?

其实很简单,只要是破坏了链表的索引结构,那么都需要备份节点的指针。什么时候会破坏链表的索引结构呢?其实就是修改next箭头的时候。

因此总结为:修改了某个节点的next箭头,此时该节点的下一个节点索引丢失,因此要备份该节点的下一个节点

1.3.3.代码实现

class Solution
{
public:struct ListNode{int val;ListNode* next;ListNode(int x): val(x), next(nullptr){}ListNode(int x, ListNode* ptr): val(x), next(ptr){}};ListNode *swapPairs(ListNode *head){ListNode* dummy_node = new ListNode(0);dummy_node->next = head;// 链表举例: [cur, 1, 2, 3, 4]// 索引位置: [cur, 1, 2, 3, 4]ListNode* cur = dummy_node;  // 从虚拟头节点0开始// 要交换的是第1和2个节点,因此他们不能是空while(cur->next != nullptr && cur->next->next != nullptr){// 关于要备份哪些节点,实际上可以先编程后面,发现哪里缺了再在这里备份ListNode* tmp1 = cur->next;ListNode* tmp3 = cur->next->next->next;// 步骤1:cur的箭头指向第2个节点,由于cur的箭头被修改,因此需要备份它原来箭头指向的节点,即tmp1cur->next = cur->next->next;// 步骤2:第2个节点的箭头指向第1个节点,由于第2个节点的箭头被修改,因此要备份它原来箭头指向的节点,即tmp3cur->next->next = tmp1;// 步骤3:第1个节点的箭头指向第3个节点cur->next->next->next = tmp3;cur = cur->next->next;   // 最后,cur跳两个位置,交换下两个节点}return dummy_node->next;  // 返回交换后的链表的头指针}
};

2.19删除链表的倒数第N个节点

参考:代码随想录19删除链表的倒数第N个节点

2.1.题目

在这里插入图片描述

2.2.思路

注意这里是求倒数第n个节点,一个比较简单的思路是把原来的链表先遍历一遍,求出来它一共有多少个节点N,然后再把总结点数N减去n,就得到了正向数要删除的是第几个节点N-n。这样再次遍历列表到第N-n个节点,对这个节点进行删除。但是很显然这样两个for循环遍历,复杂度是n^2。

另外一个很好的解法是使用双指针,一个fast和一个slow指针都指向虚拟头节点,然后fast指针先向后移动n,然后fast和slow一起向后移动,直到fast移动到最后一个节点,此时slow也就移动到了要被删除的节点。

class Solution
{
public:struct ListNode{int val;ListNode* next;ListNode(int x): val(x), next(nullptr){}ListNode(int x, ListNode* ptr): val(x), next(ptr){}};ListNode *removeNthFromEnd(ListNode *head, int n){ListNode* dummy_node = new ListNode(0, head);ListNode* fast = dummy_node;ListNode* slow = dummy_node;while(n-- && fast->next != nullptr){fast = fast->next;}// 至此fast->next就是第n个节点while(fast->next != nullptr){fast = fast->next;slow = slow->next;}// 至此fast指向最后一个节点,slow的下一个节点就是倒数第n个节点,就是要被删除的节点ListNode* tmp = slow->next;   // 这里修改了next箭头的指向,因此上面要备份原来的next箭头指向的节点slow->next = slow->next->next;delete tmp;return dummy_node->next;}
};

3.面试题02.07链表相交

参考:代码随想录面试题0207链表相交

3.1.题目

在这里插入图片描述

3.2.解答

其实链表相交很简单,一定满足是在后面相交的。所以只需要把两个链表在尾部对齐,然后从短的链表的开头开始遍历,比较两个链表对应节点的指针是否相等, 如果相等就找到了相交点。

class Solution
{
public:struct ListNode{int val;ListNode *next;ListNode(int x) : val(x), next(nullptr) {}};ListNode *getIntersectionNode(ListNode *headA, ListNode *headB){int len_a = 0;int len_b = 0;ListNode* cur_a = headA;ListNode* cur_b = headB;// 查询AB两个链表的总长度while(cur_a != nullptr){len_a++;cur_a = cur_a->next;}while(cur_b != nullptr){len_b++;cur_b = cur_b->next;}// 把两个指针恢复到头指针cur_a = headA;cur_b = headB;int offset = len_a - len_b;if(offset > 0){while(offset--){cur_a = cur_a->next;}}else{offset = -offset;while(offset--){cur_b = cur_b->next;}}while(cur_a != nullptr){if(cur_a == cur_b){return cur_b;}else{cur_a = cur_a->next;cur_b = cur_b->next;}}return nullptr;}
};

4.142.环形链表II

参考:代码随想录142环形链表II

4.1.题目

在这里插入图片描述

4.2.思路

整体思路都是参考代码随想录中的讲解,详细内容去看讲解吧,本题还是比较复杂的,从判断是否有环,到计算环的第一个入口点,都有很多分析。

注意在其中有几个问题:

  1. 为什么fast指针走两步,slow指针走一步,如果有环的话他们一定会相遇呢

其实这个问题根据讲解分为两步:
(1)如果有环,那么一定是fast先进入环。这个很容易理解,因为fast走的快嘛。
(2)当slow指针进入环之后,此时只要fast指针没有立即和slow指针相遇,那么不论fast指针在什么地方,都可以把slow指针认为是在前面运动的指针,然后fast指针就在追slow指针。fast指针每次都比slow指针多走一步,因此fast指针在一步一步的追slow指针,他们之间的距离就是一步一步的在减小,因此他们一定会相遇,最多追赶的次数也就是y+z-1次,其中y+z是环的节点个数。

为什么最多的追赶次数是y+z-1?因为slow刚进入环的时候,如果fast此时也在环的入口点,此时他们直接就相遇了,不用追赶,追赶次数就是0次。只要fast不在环的入口点,按照上面的理解,此时一律都认为slow指针在前面,fast指针在后面追slow指针。那么追的最大次数也就是fast指针恰好在slow指针的下一个节点上,然后fast指针就要比slow指针多走y+z-1次才能追上slow指针,也就是就差一步就追完一圈,这差的一步就是刚开始的时候fast指针在slow指针的下一个节点导致的。

  1. 计算入口节点的时候,为什么slow指针在环内走过的长度只可能是y,而不是n*(y+z) + y呢?即slow指针不可能走完一圈

其实这个问题在上面的问题中已经得到了解答,就是如果slow在刚进入环的时候如果和fast没有相遇,那么fast就要一步一步的去追slow,而且此时追slow需要的步骤次数最多就是y+z-1次,即不会走完一个环,所以说slow指针被fast指针追上之前,在环内肯定不可能走完一圈,最大的长度也就是走y+z-1步,也就是从环的入口开始,走到环的入口的前一个点,此时恰好被fast追上。实际上,这种情况对应的是在slow刚进入环的时候,fast恰好在环的入口的下一个节点上,然后fast就需要追y+z-1步才能追上slow指针。

  1. 最后计算相遇节点的时候,得到的公式是x = (n-1)*(y+z) + zn=1x=z,为什么n>1时结果和n=1时结果一样

其实从分析这个公式就可以知道,即一个指针从起点开始,另一个指针从相遇点开始,每次每人各走一步,最后走完x步以后他们一定会在环的起点相遇。此时如果n>1,就相当于另一个指针在环中等了从head出发的第一个指针很久,在环里绕了很多次了。但是最后的结果他们还是会在环的入口处相遇的。

class Solution
{
public:struct ListNode{int val;ListNode *next;ListNode(int x) : val(x), next(NULL) {}};ListNode *detectCycle(ListNode *head){ListNode* fast = head;ListNode* slow = head;// 注意这里只判断到fast->next即可,不需要判断到fast->next->nextwhile(fast != nullptr && fast->next != nullptr){// 这里一定要先移动再判断,而不能上来就判断是否相等,因为一开始他俩都是head,肯定是相等的fast = fast->next->next;slow = slow->next;// 找到了环的入口点if(fast == slow){// 让一个新的指针从head出发,然后每次走一步,最终会和slow指针在环的入口点相遇ListNode* new_slow = head;while(new_slow != slow){new_slow = new_slow->next;slow = slow->next;}return new_slow;}}return nullptr;}
};

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

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

相关文章

JavaEE——No.1 多线程案例

JavaEE传送门JavaEE JavaEE——No.1 线程安全问题 JavaEE——No.2 线程安全问题 目录多线程案例1. 单例模式饿汉模式懒汉模式2. 阻塞队列阻塞队列的使用阻塞队列的实现多线程案例 1. 单例模式 单例模式是一种常见的设计模式. 设计模式: 软件开发时, 会遇到一些常见的 “问…

存储系统基本概念

内容框图 存储器的层次化结构 由于cpu运行太快,所以中间需要主存,Cache,寄存器去传递。 主存–辅存(硬件操作系统):实现虚拟存贮系统,解决了主存容量不够的问题。 Cache–主存(硬件自…

测试用例设计专栏

哈喽大家好哎呀,今天给大家普及一下测试用例如何设计,看牛逼的大佬们是如何测试的。 测试用例笔试题 出题:在一个页面上有一个输入框,一个计数器(count)按钮,用于计算一个文本字符串中字母a出现的次数,请…

Linux 逻辑卷精简卷报错问题解决

一、 故障描述 现象1:oraclelog目录提示坏道信息,进行修复后执行删除文件操作,目录不可使用。 现象2:lsblk看到目录出现重复,并且有tmeta,tdata卷出现(图一) 现象3:message日志出现多目录报错,持续写入(图二) 图一 检查lv #lvs -a 看到多出的pmspare,tdata,tmeta…

VSCode 使用教程-9.Node运行js出现 Cannot use import statement outside a module的问题

前言 js中导入公共模块,使用import的方式导入,用node运行js文件会出现Cannot use import statement outside a module的问题 问题描述 目录结构 └─src└─js└─ext.js└─main.js └─index.html在ext.js 文件写一些公共方法 export const m (f…

vue3 ts vite 项目快速构建

1.安装nodejs(建议装14版本稳定) 下载 | Node.js 中文网 装完之后会有一个命令叫 npm 可以在终端输入npm -v 来检查是否安装成功2.构建vite项目官方文档开始 {#getting-started} | Vite中文网 vite 的优势冷服务 默认的构建目标浏览器是能 在 script 标签上支持原生 ESM 和…

Java操作HDFS

1. 创建maven项目 New Project 2. 添加依赖 <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client --><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-client</artifactId><version>2.…

steam搬砖汇率差项目详解

很久没有分享赚钱项目了&#xff0c;今天这里给大家介绍一个游戏搬砖的项目&#xff1a;steam游戏汇率差赚钱项目。 项目原理&#xff1a; Steam平台是一个国外游戏及装备售卖平台。而我们所谓的Steam游戏装备搬砖就是利用steam平台和网易的BUFF平台来操作。steam汇率差赚钱原…

十大经典排序算法综述(Java代码实现,思想通用)

关于十大排序的文章也有不少了&#xff0c;但感觉大部分在各个排序算法的适用场景、如何实现外排等细节方面没怎么讲&#xff0c;故总结了这篇文章&#xff0c;欢迎浏览 一、前言 内部排序是指排序时将待排序数据全部加载到内存的算法。 外部排序是指在处理海量数据排序时&…

什么是C语言?

什么是C语言&#xff1f; 文章目录什么是C语言&#xff1f;1.C语言的起源2.C语言的使用领域3. 为什么要学习C语言4.C语言的学习境界5.如何学习C语言6.学习C语言的推荐书籍1.C语言的起源 C语言之父是丹尼斯里奇&#xff1a;丹尼斯里奇&#xff08;1941年9月9日-2011年10月12日&…

Linux 简单命令 - cron 计划任务 、NTP

Linux 简单命令 - cron NTP cronNTP 一、cron 计划任务就是按照系统的时间(时刻、周期)执行指定的任务 系统服务&#xff1a; crond。 配置文件&#xff1a; /etc/crontab /var/spool/cron/用户名 配置记录格式&#xff1a; 分 时 日 月 周 任务操作命令 (用绝对路径、必要时…

集成学习详解

入门小菜鸟&#xff0c;希望像做笔记记录自己学的东西&#xff0c;也希望能帮助到同样入门的人&#xff0c;更希望大佬们帮忙纠错啦~侵权立删。 目录 一、集成学习的产生原因与相关定义 1、产生原因 2、相关定义 &#xff08;1&#xff09;同质集成 &#xff08;2&#xf…

第三章 学校与班级管理

01 学校组织与管理 02 班级与班集体 03 班主任与班主任工作 04 班级活动与班队活动 05 课外活动 02 班级与班集体 一、班级与班集体 二、班级管理 三、班级突发事件的处理 一、班级与班集体 &#xff08;一&#xff09;班级 了解 年龄、知识程度相近&#xff0c;有共同的学…

python学习—第一步—Python小白逆袭大神(第二天)

python进阶python语法继续学习数据结构数字字符串列表元组字典面向对象继承JSON异常处理try except finallyLinux命令作业来啦&#xff01;问题python语法继续学习 数据结构 数字 Number类型用于存储数值。 1、数学运算math模块及常用函数 菜鸟教程 导入math 代码示例&#…

微信小程序事件和页面跳转

一、页面跳转 1.非TabBar页面 一个小程序拥有多个页面&#xff0c;我们通过wx.navigateTo进入一个新的页面 我们通过下边点击事件实现页面跳转进行代码实现及参考 wx.navigateBack&#xff08;&#xff09;回退到上一个页面 wx.redirectTo&#xff08;url&#xff09;删除…

最新AWVS14.9.220913107 支持Windows使用教程(附下载地址)

主要内容&#xff1a;awvs14.9下载、awvs14.9使用教程、awvs14.9安装教程、awvs14.9批量扫描、awvs14.9用法、awvs最新版下载 使用方法 一键PJ 一键PJ则只需要在安装Acunetix软件后&#xff0c;运行pj工具即可 通用思路 修改hosts文件&#xff08;C:\Windows\System32\driv…

15天深度复习JavaWeb的详细笔记(十一)——VUE、Element

文章目录demo11-VUE、Element1&#xff0c;VUE1.1 概述1.2 快速入门1.3 Vue 指令1.3.1 v-bind & v-model 指令1.3.2 v-on 指令1.3.3 条件判断指令1.3.4 v-for 指令1.4 生命周期2&#xff0c;Element2.1 快速入门2.2 Element 布局2.2.1 Layout 局部2.2.2 Container 布局容器…

Spring 注解

事务注解 使用注解 EnableTransactionManagement 开启事务支持后&#xff0c;然后在访问数据库的Service方法上添加注解 Transactional 便可 * EnableTransactionManagement&#xff0c;会额外加载 4 个 bean * BeanFactoryTransactionAttributeSourceAdvisor 事务切面类 …

C# ASP.NET Web Core API (.NET 6.0)

目录 一、简介 二、创建项目 三、启动项目 四、开放访问权限 五、添加其他的API 结束 一、简介 ASP.NET Core Web API 是 ASP.NET Core MVC 的一个功能。ASP.NET Core MVC 包含了对 Web API 的支持。可以构建多种客户端的 HTTP 服务。ASP.NET Core Web API可用于在 .NET…

LeetCode 0328. 奇偶链表

【LetMeFly】328.奇偶链表 力扣题目链接&#xff1a;https://leetcode.cn/problems/odd-even-linked-list/ 给定单链表的头节点 head &#xff0c;将所有索引为奇数的节点和索引为偶数的节点分别组合在一起&#xff0c;然后返回重新排序的列表。 第一个节点的索引被认为是 奇…