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

        前言:本节内容主要是讲解链表的两个问题 :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; 所以,根据这个式子。 我们就可以证明如果从相遇节点和头节点同时向后遍历, 那么最后再次相遇时的节点就是入环节点。 

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

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

       

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

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

相关文章

基于SSM的个人博客系统(四)

目录 5.3 博客类别管理模块 5.3.1 添加博客类别 5.3.2 修改博客类别 5.3.3 删除博客类别 5.3.4 显示博客类别 5.4 评论管理模块 5.4.1 审核评论 5.4.2 删除评论 前面内容请移步 基于SSM的个人博客系统(三) 个人博客系统的设计与实现免费源码…

头歌:Spark GraphX—寻找社交媒体中的“影响力用户”

第1关:认识Pregel API 简介 Spark GraphX中提供了方便开发者的基于谷歌Pregel API的迭代算法,因此可以用Pregel的计算框架来处理Spark上的图数据。GraphX的Pregel API提供了一个简明的函数式算法设计,用它可以在图中方便的迭代计算,如最短路径、关键路径、n度关系等,也可以…

【C++】STL学习之优先级队列

🔥博客主页: 小羊失眠啦. 🎥系列专栏:《C语言》 《数据结构》 《C》 《Linux》 ❤️感谢大家点赞👍收藏⭐评论✍️ 文章目录 前言一、优先级队列的使用1.1 基本功能1.2 优先级模式切换1.3 相关题目 二、模拟实现优先级…

AI赋能不应贵气:深度解读AI助力企业渡过经济寒冬以及如何落地AI的路径

AI很棒可是给人感觉“很贵”因此我不敢用 继GPT4后Dalle3、Sora、GPT4.5、GPT5的消息以及前天突然出现的GPT 2.0(GPT二代,有人说这就是OPEN AI的新产品:Q*)但凡涉及到AI的一系列新闻给人予很震撼的感觉。放眼望去AI正在欣欣向荣。…

洛谷 P5854:【模板】笛卡尔树

【题目来源】https://www.luogu.com.cn/problem/P5854【题目描述】 给定一个 1∼n 的排列 p,构建其笛卡尔树。 即构建一棵二叉树,满足: 1.每个节点的编号满足二叉搜索树的性质。← 优先级 pri 满足二叉搜索树(BST)的性…

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

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

Web后端开发中对三层架构解耦之控制反转与依赖注入

内聚与耦合 内聚 比如说我们刚刚书写的员工的实现类 在这里我们仅仅书写的是和员工相关的代码 而与员工无关的代码都没有放到这里 说明内聚程度较高 耦合 以后软件开发要高内聚 低耦合 提高程序灵活性 扩拓展性 分析代码 如何解耦 创建容器 提供一个容器 存储东西 存储E…

基于FPGA的数字信号处理(5)--Signed的本质和作用

前言 Verilog中的signed是一个很多人用不好,或者说不太愿意用的一个语法。因为不熟悉它的机制,所以经常会导致运算结果莫名奇妙地出错。其实了解了signed以后,很多时候用起来还是挺方便的。 signed的使用方法主要有两种,其中一种…

Android View事件分发面试问题及回答

问题 1: 请简述Android中View的事件分发机制是如何工作的? 答案: 在Android中,事件分发机制主要涉及到三个主要方法:dispatchTouchEvent(), onInterceptTouchEvent(), 和 onTouchEvent(). 当一个触摸事件发生时,首先被Activity的…

配置 Trunk,实现相同VLAN的跨交换机通信

1.实验环境 公司的员工人数已达到 100 人,其网络设备如图所示。现在的网络环境导致广播较多网速慢,并且也不安全。公司希望按照部门划分网络,并且能够保证一定的网络安全性。 其网络规划如下。 PC1和 PC3为财务部,属于VLAN 2&…

邦注科技 温控箱对企业的重要性

注塑加工是将加热的熔融塑料注入模具中形成所需产品的工艺过程。良好的注塑加工工艺需要控制好许多参数,其中最重要的因素之一就是模具的温度。模具温度的不稳定会导致产品尺寸大小、表面缺陷等方面的问题,甚至会导致生产不良品,加大生产成本…

Educational Codeforces Round 165 (Rated for Div. 2 ABCDE 题)视频讲解

A. Two Friends Problem Statement Monocarp wants to throw a party. He has n n n friends, and he wants to have at least 2 2 2 of them at his party. The i i i-th friend’s best friend is p i p_i pi​. All p i p_i pi​ are distinct, and for every i ∈…

通义灵码实战系列:一个新项目如何快速启动,如何维护遗留系统代码库?

作者:别象 进入 2024 年,AI 热度持续上升,翻阅科技区的文章,AI 可谓是军书十二卷,卷卷有爷名。而麦肯锡最近的研究报告显示,软件工程是 AI 影响最大的领域之一,AI 已经成为了软件工程的必选项&…

FLUKE万用表17B+的电压档最大内阻

项目中遇到一个测量兆欧级别电阻两端电压的问题,发现按照上图中的电路搭建出来的电路测得的电压为8.25V左右,按理说应为9V才对,后来想到万用表测量电压档不同的档位会有不同内阻,测量的电阻应远小于万用表电压档内阻才有效。本次测…

顶尖页面性能优化跃升之道:uniapp首屏加载性能极致优化策略权威指南(白屏现象终结攻略)

页面加载性能优化至关重要,直接影响用户体验满意度及网站流量转化。优化加载性能可以减少用户等待时间,提升交互响应,有效减少出现白屏的情况,增加用户留存,同时有利于搜索引擎排名,对网站流量、品牌形象及…

【常规】解决win11的Edge浏览器掉线问题

文章目录 【问题】【解决】step1 右键点击wifi--【网络和Internet设置】step2 点击打开后,打开【高级网络设置】后边的箭头step3 进入下一级以后,点击【WLAN】右侧的箭头step4 【更多适配选项】--【编辑】step5 取消Internet协议版本6(TCP/IP…

php反序列化字符串逃逸

字符串逃逸 字符串逃逸是通过改变序列化字符串的长度造成的php反序列化漏洞 一般是因为替换函数使得字符串长度发生变化,不论变长还是变短,原理都大致相同 在学习之前,要先了解序列化字符串的结构,在了解结构的基础上才能更好理解…

Qt Creator导入第三方so库和jar包——Qt For Android

前言 之前了解了在Android Studio下导入so库和jar包,现在实现如何在Qt上导入so库和jar包。 实现 下面是我安卓开发(需调用安卓接口的代码)的目录(图1),此目录结构和原生态环境(Android Studi…

PS证件照

证件照尺寸 小一寸:2.2cm*3.3cm 一寸:2.5cm*3.5cm 像素413*295 (分辨率为300像素/英寸) 比例5:7 二寸:3.5cm*4.9cm 二寸照相比例是4:3,像素是626*413 蓝底:R&a…

python学习之词云图片生成

代码实现 import jieba import wordcloudf open("D:/Pythonstudy/data/平凡的世界.txt", "r", encoding"utf-8") t f.read() print(t) f.close() ls jieba.lcut(t) txt " ".join(ls)w wordcloud.WordCloud(font_path"D:/cc…