数据结构算法——链表带环问题——数学深度解析

        前言:本节内容主要是讲解链表的两个问题 :1、判断链表是否带环; 2、一个链表有环, 找到环的入口点。 本节内容适合正在学习链表或者链表基础薄弱的友友们哦。

        我们先将问题抛出来,友友们可以自己去力扣或者牛客网去找相应题目, 这里直接贴链接:(没有做过这两个题的友友 千万! 千万! 千万! 要先自己做一下这两个题。)

        判断链表是否带环:141. 环形链表 - 力扣(LeetCode)

        带环链表的入环节点:LCR 022. 环形链表 II - 力扣(LeetCode)

        

目录

判断链表是否带环

题目解析

算法原理

算法演示

算法原理

原理扩展

环形链表的入口节点

题目解析

算法原理

算法演示

算法原理


        我们先来讲解第一道题

判断链表是否带环

题目解析

题目:

        

代码框:

        题目非常的简单, 就是要求我们设计一个算法, 判断这个链表中是否右带环结构就可以了。 如果有带环结构, 那么就返回true, 如果没有带环结构, 那么就返回false。 

算法原理

算法演示

        解决这个问题需要用到快慢双指针算法, 我们利用题中所给示例进行演示:

        先定义两个指针slow, fast。并且slow和fast要指向同时指向头节点, 否则在第二道题的时候处理起来会变的复杂

        然后, 我们向后进行遍历, 遍历的过程是这样的: slow指针一次向后移动一个节点。 fast一次向后移动两个节点。当两个节点相遇的时候就说明我们的链表是带环的。

        而如果我们的fast指向了空节点, 那么就说明我们的链表是不带环的。

        我们演示一遍是这样的:

代码贴图如下:

bool hasCycle(struct ListNode *head) 
{//先判断下链表为空的情况if (head == NULL) return false;//1、创建两个指针, slow, fast同时指向链表头节点。struct ListNode* slow = head;struct ListNode* fast = head;//2、遍历整个链表, 判断是否有环while (fast != NULL && fast->next != NULL){slow = slow->next;fast = fast->next->next;if (slow == fast) return true;  //如果两个指针指向同一个位置, 说明有环。}return false;             //退出循环说明无环。
}

        这就是算法的基本思路, 我们接下来进行剖析这个算法:

算法原理

        

        为了证明我们的结论的普遍性, 我们利用上面的抽象图来进行演示。

         这里的环周长就相当于C个节点, 前面的直线L就相当于没有进入环之前的L个节点。 然后slow每次走一个节点, fast一次走两个节点。 

        fast走的比slow要快, 所以, fast一定是在slow前面的。如果没有环的话, fast是肯定会先一步指向空的。 fast和slow也就无法相遇了。 所以如果fast指向空, 那么就一定没有环。

         但是如果有环, 这个时候fast一定会先进入环。如图所示:

        然后继续遍历, slow再进入环。如图所示:

        那么, 重点就来了。 slow进入环之后, 如果fast和slow再继续遍历, 那么是不是fast和slow之间的距离在不断的缩小, 是不是就相当于fast在追击slow。 我们假设fast到slow的距离为N, 此时我们就将问题转化为了一个追击相遇问题。  

        fast每次走两步, slow每次走一步。 那么每回合fast和slow之间的距离就减少1。 循环下来就是N - 1  - 1 - 1 - 1 - 1, 直到N为零位置。 此时fast和slow相遇。并且当这两个指针相遇的时候, 只有两种情况, 一个是在环的入口点相遇, 一个是在环的其他位置相遇。 但这两种情况可以归到一类里面——slow和fast会在环内相遇

        

 综上, slow和fast只要相遇了, 他们就会在环内, 说明链表有环。

        然后到这里这个题的算法原理基本结束了, 但是, 这里还有一个经常考的探究性问题, 很重要的扩展知识。 接下来进行分析:

原理扩展

        从上文我们知道, 当slow指针和fast指针相遇的时候一定再环内。 但是有没有可能slow和fast不会相遇, 每次fast指针都越过slow指针, 导致两个指针永远无法相遇?

        答案是我们前面分析的fast走两步, slow的情况不会。

        但是如果fast一次走三步, slow一次走一步以及一些其他情况就可能fast直接越过slow, 永远不会相遇。这里需要具体情况具体分析。

        分析过程如下:

        如果我们slow每回合走一步, fast每回合走三步。 那么fast和slow的步数就相差2。 假设slow进环的时候slow和fast之间的距离相差N。那么循环下来就是N - 2 - 2 - 2. 这里如果N是偶数, N就恰好减少到零了, 这个时候slow和fast指针就相遇了。

        但是如果N是奇数呢? N - 2 - 2 - 2就有可能得到-1,我们假设圆环的长度为C,这个时候就是如图这种情况:

        那么, fast和slow之间的距离此时变成了C - 1

        接下来再进行分类讨论, 如果C为奇数, 那么C - 1就为偶数, 这样 C - 1 - 2 - 2 - 2……就有可能减少到零; 如果C为偶数, 那么C - 1就是奇数, 这样 C - 1 - 2 - 2 ……就会重新减少到-1, 然后fast和slow之间的距离又变成C - 1, 这个时候就陷入了死循环。

         所以,综上我们可以得出小结论: 当fast一次走三步, slow一次走一步。如果N为偶数, 那么fast和slow一定相遇。 如果N为奇数, C为奇数。 那么fast和slow也会相遇。 如果N为奇数, C为偶数, 那么fast和slow永远不会相遇。

        将上面的推导过程总结为一个算数表达式就是 : (N + x * C)% 2  ? 0//距离N + x圈后模上fast和slow步数差是不是等于0.

        所以, 我们得出的大结论就是:假设slow和fast每回合的步数相差sub.进入环的时候fast指针与slow指针之间的距离为N。圆环的长度为C。如果有 :  (N + x * C)% sub  ==  0。就说明fast和slow会相遇, 否则不会相遇。

        以上就是本道题的所有知识点。

        ps:代码中的细节问题,属于代码编写的范畴, 不属于算法原理。 本篇内容只讲算法原理。 代码友友们自行编写调试。



环形链表的入口节点

题目解析

题目:

代码框: 

        

 这道题就是上面那道题的提高版本。 需要先对链表判断是否有环, 然后再判断入环节点。

算法原理

要找到环的入口点同样是有结论的, 这里我们先用结论进行代码的展示。 再进行分析

算法演示

        寻找环的入口点的算法就是先利用上面一题的方法先判断是否存在环。 这个时候如果存在环的话fast指针和slow指针会在环内的某一个点相遇。

        然后,我们定义一个meet指针指向这个相遇节点。 然后再重新定义一个指针指向链表的头节点。 让meet指针和指向头节点的指针同时向后遍历。 最后相遇的节点就是我们环的入口节点。 (原理是数学证明, 后面会进行证明。)

        如图为代码贴图:

struct ListNode *detectCycle(struct ListNode *head) 
{//1、定义快慢指针struct ListNode* slow = head;struct ListNode* fast = head;//2、循环判断是否有环while (fast != NULL && fast->next != NULL){//fast走两步, slow走一步。slow = slow->next;fast = fast->next->next;//如果相遇, 说明有环。 然后寻找入环节点if (slow == fast){//meet节点指向fast和slow相遇节点。struct ListNode* meet = slow;//让slow重新指向头节点slow = head;//如果两个指针不相等, 就让他们向后遍历。while (slow != meet){slow = slow->next;meet = meet->next;}//最后返回相遇节点return meet;}}return NULL;  //从循环中出来说明fast走向了空, 说明没有环。
}

算法原理

        要证明为什么从相遇位置和头节点的两个指针同时向后遍历, 相遇节点就是入环节点。我先给一张抽象图, 方便观察与理解:

假设我们从图中时刻开始向后遍历。 首先, fast先进环。如下图:

        然后, fast继续向后走。 一直到slow进环:

        好, 在这里停住, 这里有很重要的问题。 就是这个fast此时在环中已经走了几圈? 

        这个圈数确定吗?答案是不确定。 因为我们并不知道这个圈的大小, 如果这个圈很小很小。 然后前面的直链很长, 那么从fast进环到slow进环这一段时间中fast就可能在环中转了很多很多圈。

        我在这画出一个例子就好懂了:

        从这个例子我们可以看出, 在fast到slow这段时间内, fast在环中走的圈数是不确定的。

        知道了这点后, 我们继续向下遍历, 一直到slow和fast相遇。

        现在, 另外一个重要的问题就是:从slow进环到被fast追上, slow转了几圈?

        我们看这样一个例子:

        如果当slow进环的时候, fast恰好在slow前面一个位置。 如图:

        那么到fast追上slow的时候, slow转的了一圈吗?

        我们利用方程算一下:假设slow行走的路程是s, 那么fast就是2s。 此时就有:2s - s = C - 1;

得到的就是s == C - 1; 很显然就算在这种极端情况下slow都没有走一圈, 那么其他情况下更不可能走一圈。 那么上面的问题的答案就是: 从slow进环到被fast追上, slow一圈也没有转。

        有了这些的铺垫后。 我们再从整体出发看 : 从两个指针开始遍历到两个指针相遇。 设slow行走的距离是X。 那么fast指针行走的距离就是2 * X;  我们设从入环到节点到slow和fast相遇的位置的距离为N。如图:

        那么就有N + L = X;所以slow行走的距离就是N + L;fast行走的距离就是2 *(N + L)

        但是, 对于fast来说, 还有另外一个式子: fast在从入环到slow入环期间,我们分析过了。 fast可能行走了几圈。 我们设为k圈。  然后从入环节点到相遇位置为N。 那么就有了fast的行走距离又可以有 : L + k * C + N;

        那么就有  2 *(N + L)== L + k * C + N

        计算后就是k* C - N == L;  也可以写成 : (k - 1) * C + (C - N) == L

        而我们的相遇节点到入环节点的距离恰好是 C - N; 所以,根据这个式子。 我们就可以证明如果从相遇节点和头节点同时向后遍历, 那么最后再次相遇时的节点就是入环节点。 

---------------------------------------------------------------------------------------------------------------------------------

        以上, 就是本节的全部内容。

       

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

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

相关文章

【源码解析】深入Pandas的心脏DataFrame 含十大功能、源码实现与编程知识点

作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。 会一些的技术:数据分析、算法、SQL、大数据相关、python 欢迎加入社区:码上找工作 作者专栏每日更新: LeetCode解锁1000题: 打怪升级之旅 python数据分析…

微信私域生态下的企业级私域建设:开源AI智能名片商城小程序源码六大模块功能价值深度解读

在数字化营销蓬勃发展的今天,企业如何在微信私域生态中构建并运营一个稳固的私域流量池,成为了摆在众多企业家和市场人面前的重要课题。本文将基于开源AI智能名片B2B2C商城小程序源码的AARRR模型,深度解读微信私域中企业级私域建设的六大模块…

文心一言 VS 讯飞星火 VS chatgpt (249)-- 算法导论18.2 2题

二、请解释在什么情况下(如果有的话),在调用 B-TREE-INSERT 的过程中,会执行冗余的 DISK-READ 或 DISK-WRITE 操作。(所谓冗余的 DISK-READ ,是指对已经在主存中的某页做 DISK-READ 。冗余的 DISK-WRITE 是…

【C语言/数据结构】经典链表OJ习题~第二期——链中寻环

🎈🎈🎈欢迎采访小残风的博客主页:残风也想永存-CSDN博客🎈🎈🎈 🎈🎈🎈本人码云 链接:残风也想永存 (FSRMWK) - Gitee.com🎈&#x1f…

第3篇:创建Nios II工程之Hello_World<二>

Q:上一期介绍完基本设计流程和实验原理,接着我们完成系统硬件设计部分,包括Platform Designer系统及Quartus工程。 A:依次搜索并添加Nios II Processor、JTAG UART、On-Chip Memory和System ID IP组件,连接各组件并As…

随笔Ubuntu上的的一些使用

Ubuntu简易使用 常用指令 cdlsmkdirrf -rm 路径 换源 备份镜像 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak编辑文件设置 sudo gedit /etc/apt/sources.list清华源 # 阿里源 deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe mul…

STM32 F103C8T6学习笔记17:类IIC通信—MLX90614红外非接触温度计

今日学习配置MLX90614红外非接触温度计 与 STM32 F103C8T6 单片机的通信 文章提供测试代码讲解、完整工程下载、测试效果图 本文需要用到的大概基础知识:1.3寸OLED配置通信显示、IIC通信、 定时器配置使用 这里就只贴出我的 OLED驱动方面的网址链接了&#xff1a…

2024深圳杯数学建模竞赛D题(东三省数学建模竞赛D题):建立非均质音板振动模型与参数识别模型

更新完整代码和成品完整论文 《2024深圳杯&东三省数学建模思路代码成品论文》↓↓↓(浏览器打开) https://www.yuque.com/u42168770/qv6z0d/zx70edxvbv7rheu7?singleDoc# 2024深圳杯数学建模竞赛D题(东三省数学建模竞赛D题&#xff0…

口袋实验室--使用AD2学习频谱参数测试

目录 1. 简介 2. 频谱相关参数 2.1 频谱相关基本概念 2.1.1 采样时间间隔 2.1.2 采样频率 2.1.3 采样点数 2.1.4 采样时间长度 2.1.5 谱线数 2.1.6 奈奎斯特频率 2.1.7 频谱分辨率 2.1.8 最高分析频率 2.1.9 频谱泄露 2.2 窗函数 2.2.1 AD2的窗函数 2.2.2 测试矩…

Github 2024-05-01 开源项目月报Top20

根据Github Trendings的统计,本月(2024-05-01统计)共有20个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目13TypeScript项目5C项目2非开发语言项目1C++项目1JavaScript项目1Rust项目1Go项目1Shell项目1Svelte项目1编程面试大学:成为软件工程…

报错Unable to install JS,且提示Unable to run npm install【鸿蒙报错已解决】

文章目录 项目场景:问题描述原因分析:解决方案:此Bug解决方案总结Bug解决方案寄语项目场景: 最近遇到了这个问题,看到网上也有人在询问这个问题,实操了很多网上的解决方案发现并不能解决这个Bug,所以我在解决这个问题后,总结了自己和其他人的解决经验,进行了整理,写…

Qt的qtmqtt库连接onenet出现QMQTT::SocketRemoteHostClosedError解决方法

问题描述 在Qt发开过程中使用qtmqtt库来连接onenet的mqtt服务器,在ClientId、Username和Password均填写正确的情况下还是连接不上,查看错误显示QMQTT::SocketRemoteHostClosedError。 解决方法 client中的CleanSession标志位必须设置为true。 client …

Nginx负载均衡主备模式

1. 背景 使用Nginx代理后端服务,有时候某些服务是不能使用多台负载均衡,但又想保障高可用,所以采用主备模式,记录如下: 2. 参考 nginx 负载均衡Nginx-负载均衡-后端状态max_conns、down、backup、max_fails、fail_t…

Thinkphp--in-sqlinjection

一、漏洞原理 在 Builder 类的 parseData 方法中&#xff0c;由于程序没有对数据进行很好的过滤&#xff0c;将数据拼接进 SQL 语句&#xff0c;导致 SQL注入漏洞 的产生。 影响版本 5.0.13<ThinkPHP<5.0.15 5.1.0<ThinkPHP<5.1.5 在相应的文件夹位置打开终端…

RMQ从入门到精通

一.概述与安装 //RabbitMQ //1.核心部分-高级部分-集群部分 //2.什么是MQ 消息队列message queue 先入先出原则;消息通信服务 //3.MQ的大三功能 流量消峰 应用解耦 消息中间件 //&#xff08;1&#xff09;人-订单系统(1万次/S)—> 人 - MQ(流量消峰,对访问人员进行排队) -…

服务器数据恢复—服务器重装系统导致XFS分区丢失的数据恢复案例

服务器数据恢复环境&#xff1a; 一台服务器MD1200磁盘柜&#xff0c;通过raid卡将15块磁盘组建成一组raid5磁盘阵列。raid5阵列分配了2个lun&#xff0c;操作系统层面对lun进行分区&#xff1a;1个分区采用LVM扩容方式加入到了root_lv中&#xff0c;其余分区格式化为XFS文件系…

强化学习(Reinforcement learning)基本概念

概念&#xff1a; 强化学习是在与环境互动中为达到一个目标而进行的学习过程 三层结构&#xff1a; 基本元素&#xff1a;agent、environment、goal agent&#xff1a;可以理解为玩家&#xff0c;即某个游戏的参与方 environment&#xff1a;环境本身&#xff0c;可以理…

数据结构复习指导之串的模式匹配

文章目录 串的模式匹配 考纲内容 复习提示 1.简单的模式匹配算法 知识回顾 2.串的模式匹配算法——KMP算法 2.1字符串的前缀、后缀和部分匹配值 2.2KMP算法的原理是什么 3.KMP算法的进一步优化 串的模式匹配 考纲内容 字符串模式匹配 复习提示 本章是统考大纲第6章内…

Android开发知识杂录

1.XML解析问题 增加XML布局文件时候出现 mergeDebugResources 错误 解决方案 由于XML默认文件带有BOM&#xff0c;remove bom即可 2.开机启动界面添加 3.开机隐藏系统桌面 4.添加敲击传感器GPIO 1. 测试板子的GPIO引脚情况 echo in > /sys/class/gpio/gpio<gpio_number…

排序-八大排序FollowUp

FollowUp 1.插入排序 (1).直接插入排序 时间复杂度:最坏情况下:0(n^2) 最好情况下:0(n)当数据越有序 排序越快 适用于: 待排序序列 已经基本上趋于有序了! 空间复杂度:0(1) 稳定性:稳定的 public static void insertSort(int[] array){for (int i 1; i < array.length; i…