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

        前言:本节内容主要是讲解链表的两个问题 :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.xdnf.cn/news/1409651.html

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

相关文章

el-tabs作为子组件使用页面空白

文章目录 前言一、问题展示二、源码分析三、解决方案 前言 如果el-tabs是子组件,父组件传值value / v-model为空字符,这个时候在watch中监听value / v-model就会发现监听的数据会被调用为‘0’。一定是作为子组件引用,且在watch进行监听&…

【Java探索之旅】包管理精粹 Java中包的概念与实践

文章目录 📑前言一、封装1.1 封装的概念1.2 访问限定修饰符 二、封装扩展(包)2.1 包的概念2.2 带入包中的类2.3 自定义包2.4 常见的包 🌤️全篇总结 📑前言 在Java编程中,封装是面向对象编程的核心概念之一…

令牌技术详解

1. 问题引出 之前我们讲 Cookie 和 Session 时提到过一个用户登录的场景:当用户登录时,服务器端可以把用户的登录信息存在Session中 并返回给客户端对应的SessionID,客户端会把这个SessionID存在Cookie 中当下次访问该服务器时,…

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

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

JUC并发-共享模型-不可变

1、日期转换的问题 下面的代码在运行时,由于 SimpleDateFormat 不是线程安全的 Slf4j(topic "c.Test1") public class Test1 {public static void main(String[] args) {SimpleDateFormat sdf new SimpleDateFormat("yyyy-MM-dd");for (int…

Mac系统常用操作

文章目录 1、常用快捷键2、常用功能及其操作 1、常用快捷键 win和mac键盘对比:Command按键和Ctrl按键类似, 图片来源:https://www.xiaohongshu.com/explore/62d2787a0000000011012ab9锁屏:ControlCommandQ复制、粘贴、剪切、全选…

hdc不是内部或外部命令,也不是可运行的程序或批处理文件。【鸿蒙报错已解决】

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

linux系统的rsync命令实现本机到远程主机之间目录的复制和同步

一、rsync命令介绍 在Linux中,rsync 是一个强大的命令行工具,用于同步文件和目录。它可以在本地或通过网络在远程系统之间复制文件。 二、远程目录复制的条件 1、系统要已经安装rsync工具 要使用 rsync 复制远程目录,需要确保系统上安装了 …

知识图谱与知识表示:人工智能的基石

知识图谱与知识表示:人工智能的基石 一、知识图谱:连接数据的桥梁1.1 知识图谱的构成1.2 知识图谱的应用 二、知识表示:AI的推理基础2.1 知识表示的定义2.2 知识表示的形式 三、从符号表示到向量表示3.1 符号表示与向量表示3.2 向量表示的优势…

自动化机器学习——网格搜索法:寻找最佳超参数组合

自动化机器学习——网格搜索法:寻找最佳超参数组合 在机器学习中,选择合适的超参数是模型调优的关键步骤之一。然而,由于超参数的组合空间通常非常庞大,手动调整超参数往往是一项耗时且困难的任务。为了解决这个问题,…

算法入门<二>:分治算法之汉诺塔问题及递归造成的栈溢出

1、分治算法 分治(divide and conquer),全称分而治之,是一种非常重要且常见的算法策略。分治通常基于递归实现,包括“分”和“治”两个步骤。 分(划分阶段):递归地将原问题分解为两…

PyCharm 2024新版图文安装教程(python环境搭建+PyCharm安装+运行测试+汉化+背景图设置)

名人说:一点浩然气,千里快哉风。—— 苏轼《水调歌头》 创作者:Code_流苏(CSDN) 目录 一、Python环境搭建二、PyCharm下载及安装三、解释器配置及项目测试四、PyCharm汉化五、背景图设置 很高兴你打开了这篇博客,如有疑问&#x…

小浪助手:下载学浪视频的最佳助手

小浪助手我已经打包好了,有需要的自己下载一下 学浪下载器链接:百度网盘 请输入提取码 提取码:1234 --来自百度网盘超级会员V10的分享 1.首先解压好我给大家准备好的压缩包 2.打开小浪助手.exe 3.选择一种登录方式,扫码登录或者手机号…

【办公类-26-02】20240423 UIBOT学分自动评价(自动登录、评价和退出,全自动)

背景需求: 我想用UIBOT自动模拟鼠标,登录每位老师的账户,进入评价区域,自动选择7次“满意”,输入1次“无”,然后提交。 C Dim objExcelWorkBook,arrayRet,iRet,temp,iPID,hWeb,dictRet,XobjExcelWorkBook …

警惕虚假宣传:GPT-4.0免费领取真相揭秘

警惕虚假宣传:GPT-4.0免费领取真相揭秘 在人工智能技术飞速发展的今天,尤其是OpenAI推出的GPT-4.0成为技术前沿的焦点,不少不法分子也开始借机进行欺诈。网络上出现了大量声称“免费领取GPT-4.0”的虚假信息,这不仅误导了公众&am…

latex使用bib引用参考文献时,正文编号顺序乱序解决办法,两分钟搞定!

一、背景 用Latex写文章时,使用bib添加参考文献是一种最为简便的方式。但有的期刊模板,如机器人顶会IROS,会出现正文参考文献序号没按顺序排列的情况,如下图所示。按理说文献[4]应该是文献[2],[2]应该是[3]&#xff0…

计米功能块(CODESYS 完整ST源代码)

1、S7-1200测速计米功能块 S7-1200高速计数器编码器线速度测量(独立测速FB计米FB)_s7-1200高速计数器编程实例-CSDN博客文章浏览阅读646次。线速度工程中有很多采集方法,这里不再细述。博途PLC的高速计数器编程应用大家可以查看下面相关应用文章:计米轮…

代码随想录算法训练营DAY46|C++动态规划Part8|139.单词拆分、多重背包理论基础、背包问题总结篇

文章目录 139.单词拆分思路CPP代码 多重背包理论基础处理输入把所有个数大于1的物品展开成1个开始迭代,计算dp数组代码优化 背包问题总结篇 139.单词拆分 力扣题目链接 文章讲解:139.单词拆分 视频讲解:你的背包如何装满?| LeetCo…

70.网络游戏逆向分析与漏洞攻防-角色与怪物信息的更新-整理与角色数据更新有关的数据

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 如果看不懂、不知道现在做的什么,那就跟着做完看效果 现在的代码都是依据数据包来写的,如果看不懂代码,就说明没看懂数据包…

神经网络中常见的激活函数:理解与实践

神经网络中常见的激活函数:理解与实践 在神经网络中,激活函数是一个非常重要的组成部分,它为神经元引入了非线性特性,使得神经网络可以拟合各种复杂的函数关系。本文将介绍9种常见的激活函数,包括它们的概述、公式以及…