
文章目录
🌈
上一篇 【神秘海域】数据结构与算法 内容是 单链表及其接口
而本篇内容是对单链表的一个 非常重要 的补充: 带环单链表 。它,是大厂面试时可能会提问的内容,非常的重要!
本篇就是要结合题目来 详细分析一下 单链表的带环问题
🌈
在详细分析 带环单链表 之前,先分析两道题来了解一个非常重要的算法思路:快慢指针
🌈
原题描述是这样的:
给定一个头结点为
head的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
示例 1:输入:
[1,2,3,4,5]
输出:此列表中的结点3 (序列化形式:[3,4,5])
返回的结点值为3 (测评系统对该结点序列化表述是 [3,4,5])
注意,我们返回了一个ListNode类型的对象ans,这样:
ans.val = 3,ans.next.val = 4,ans.next.next.val = 5, 以及ans.next.next.next = NULL.
示例 2:输入:
[1,2,3,4,5,6]
输出:此列表中的结点4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为3和4,我们返回第二个结点原题链接: Leetcode - 876. 链表的中间结点
这一题的解法,就需要使用到 快慢指针 的思路
那么什么是 快慢指针 ?即,使用两个 移动速度不同 的指针在 数组 或 链表 等 序列结构上移动。
本题思路:
求链表的中间节点,就可以定义两个指针 :
pslow 慢指针、pfast 快指针在本题中,快指针每次
移动两个节点,慢指针每次移动一个节点,当快指针走过尾节点为空(链表节点为单数) 或 指向尾节点(链表节点为双数)时,慢指针应该正好在中间节点此时
慢指针所指节点即为题目所求
代码实现:
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode* pfast = head;
struct ListNode* pslow = head;
while(pfast && pfast->next)
{
pfast = pfast->next->next;
pslow = pslow->next;
}
return pslow;
}

🌈
此题描述是这样的:
例如,输入
{1,2,3,4,5}, 2时,对应的链表结构如下图所示:
其中蓝色部分为该链表的最后2个结点,所以
返回倒数第2个结点(也即结点值为4的结点)即可,系统会打印后面所有的节点来比较。
示例 1:输入:
{1,2,3,4,5},2返回值:
{4,5}说明:返回倒数第2个节点4,系统会打印后面所有的节点来比较。
示例 2:输入:
{2},8返回值:
{}
本题的思路也是使用快慢指针,但是与上一题不同的是,本题是先走为快指针 与 后走为慢指针
本题思路:
定义两个指针 :
pslow 慢指针、pfast 快指针,两指针均一步一步走快指针 先走
k步,但同时要保证快指针没走到空,如果k步没走完就已经走到空了,就表示链表没那么长然后 慢指针 与 快指针 同时开始走,直到快指针走到空
此时
慢指针所指节点即为题目所求
代码实现:
struct ListNode* FindKthToTail(struct ListNode* pHead, int k )
{
struct ListNode* pfast = pHead;
struct ListNode* pslow = pHead;
while(k--)
{
if(pfast)
{
pfast = pfast->next;
}
else
{// 快指针指向空,即链表长度不到 k,直接返回 NULL
return NULL;
}
}
while(pfast)
{
pfast = pfast->next;
pslow = pslow->next;
}
return pslow;
}

在分析带环链表之前,需要 需要了解一下 快慢指针 ,因为 带环链表的分析 是根据 快慢指针 分析的.
🌈
分析 带环链表 ,先 由一道题来引入:
🌈
此题描述:
给你一个链表的头节点
head,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪
next指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)。(注意:pos不作为参数进行传递 。仅仅是为了标识链表的实际情况)如果链表中存在环 ,则返回
true。 否则,返回false。
示例 1:
输入:
head = [3,2,0,-4], pos = 1
返回:true
解释:链表中有一个环,其尾部连接到第二个节点
示例 2:
输入:
head = [1,2], pos = 0
返回:true
解释:链表中有一个环,其尾部连接到第一个节点
示例 3:
输入:
head = [1], pos = -1
返回:false
解释:链表中没有环原题链接:Leetcode - 141. 环形链表
本题的思路也非常简单:
如果链表带环,那么使用
快慢指针:pfast一次走两步,pslow一次走一步两个指针就一定能相遇,因为
两指针均入环之后,两指针的距离是在一步步靠近的不能相遇,就代表
链表不带环
代码实现:
bool hasCycle(struct ListNode *head)
{
if(head == NULL)
return false;
struct ListNode* pfast = head;
struct ListNode* pslow = head;
while(pfast && pfast->next)
{
pfast = pfast->next->next;
pslow = pslow->next;
if(pfast == pslow)
return true;
}
return false;
}

OK,带环链表的题做出来了
但是并没有结束 如果只是这样 怎么会有大厂提问呢?
🌈
在 链表带环 的基础上,还会延伸出几个问题:
入环节点 ?这才是 带环链表 真正需要知道的东西~
🌈
🌈
快指针一次走两步,慢指针一次走一步,两指针一定会相遇吗?为什么?
来详细分析一下:
画图抽象图来分析,一个带环链表,抽象的形式可以看作:
快慢指针 同时 从首节点开始走,快指针走得快,慢指针走得慢
所以慢指针入环时,快指针早就已经入环了
此时的情况可能是(设一下,只是假设):
两个指针都入环之后,快指针开始在环内追逐慢指针:

因为 当这样的两个指针都入环之后,两个指针之间的距离变化就变为了 每走一步减一
所以,必定会相遇
🌈
如果 快指针一次走三步呢?四步呢?为什么?
快指针一次走多步,就需要看情况来分析了
快指针一次走三步:
上边我们分析了 快指针一次走两步 时的相遇情况:当两个指针都入环之后,其之间的距离是以 每次缩小 1 变化的
那么如果 快指针一次走三步,那么 两个指针都入环之后,其之间的距离就是 以 每次缩小 2 变化的
每次缩小 2,会造成什么情况呢?
设 慢指针入环时,快指针和慢指针之间的距离为
X,环的长度为C,那么就会有两种情况:当X为奇数,当X为偶数
情况 1:X为 偶数当
X(两指针之间的距离)为偶数,两指针距离又是每次减2的变化,所以一定能相遇
情况 2:X为 奇数此情况,快指针 超过 慢指针,但是由于快指针的移动是不连续的,所以两指针并不会相遇
其之间的距离变成了
-1,但是现在并不能直接判断是否能相遇,因为不能保证后面的追击能不能相遇又因为 我们设环的长度为
C,所以此时 两指针之间的距离也是C-1所以,就又分为了两种情况:当
C-1为奇数,当C-1为偶数当
C-1为 偶数的时候,这时,下次追击就可以相遇当
C-1为 奇数的时候,这时,就永远不会相遇了为什么永远不会相遇?
当
C-1为奇数时,也就意味着本次追击的X(两指针之间的距离)为奇数
X为奇数,就又会出现 两指针之间的距离等于-1的情况,距离就有变成了C-1所以,当
C-1为奇数时,永远不会遇到
快指针一次走四步:
当快指针 一次走四步 的时候,按照 一次走三步 的思路进行分析
X 为 3 的倍数,可以相遇X 不为 3 的倍数,且 C-1 或 C-2 也不为 3 的倍数,就永远无法相遇C-1 和 C-2 ,需要更详细的分析也就是说,快指针 一次走多步 能不能与慢指针相遇是 不确定的。
实际的情况,与 环的长度 和 入环前链表的长度 都有关系,需要 具体情况具体分析
🌈
怎么找到带环链表的 入环节点 ?
能够找到入环节点的一个前提是:快指针已经与慢指针相遇。
详细分析一下:
首先还是画图假设一下:
先思考一个问题:慢指针
从入环到被追上,走过的长度 是不是如假设的那样,会不会已经走了一圈后才被追上的?答案是:
不会。即使环再小,只有一个节点,慢指针那么在入环的一刻,就已经与快指针相遇了
如果环再长,慢指针也不可能走过一圈,因为快指针的速度是慢指针的两倍,慢指针如果走
超过一圈,那么快指针只会走超过两圈所以,慢指针一定是
在一圈之内被追上的,所以假设 是成立的。
参考图来看,慢指针 从开始 到 与快指针相遇,走过的距离就是 :L + X
那么 快指针 走过的距离就是 : 2 * (L + X)
快指针走过的距离还可以怎么表示呢?
快指针走过的距离 还可以这样表示:L + X + N * C (N表示走过的圈数)
因为 快指针先入环,所以在慢指针入环之前,快指针很可能在环内已经走过几圈了
- 当
L很大C很小时,快指针可能已经走了N圈了- 当
L很小C很大时,快指针可能没有走超过一圈
所以,快指针 从开始 到 与慢指针相遇 走过的距离,就可以写成一个等式:
2 * (L + X) = L + X + N*C
化简一下就是: L + X = N * C
这个式子有什么用呢?

其实,这个等式说明:
如果,有两个指针同时以一次一步的速度,一个从 链表的首节点 开始,另一个从 快慢指针相遇点 开始,两个指针会在环的入口节点相遇。
为什么呢?
L + X = N * C 可以写为 --> L = N * C - X
一个指针从 链表首届点开始走,走过 L 长度 它的位置在入环节点
一个指针从 快慢指针相遇点 开始走, 走过 N * C 的长度,它的位置还在 快慢指针相遇点 ,但是如果走过 N * C - X 的长度,那么它的位置就也在 入环节点了,因为 入环节点到快慢指针相遇点的距离是 X
此时,入环节点就找到了。
🌈
分析完如何寻找入环节点,下面来尝试把这道题给做了:
题目描述:
给定一个链表的头节点
head,返回链表开始入环的第一个节点。 如果链表无环,则返回null。如果链表中有某个节点,可以通过连续跟踪
next指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数
pos来表示链表尾连接到链表中的位置**(索引从 0 开始)**。如果pos是-1,则在该链表中没有环。注意:
pos不作为参数进行传递,仅仅是为了标识链表的实际情况
不允许修改 链表
示例 1:
输入:
head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点
示例 2:
输入:
head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点
示例 3:
输入:
head = [1], pos = -1
输出:返回 null
解释:链表中没有环
代码实现:
// 大体思路与判断有环差不多
// 但是 有环时不能直接返回
struct ListNode *detectCycle(struct ListNode *head)
{
if(head == NULL)
return NULL;
struct ListNode* pfast = head;
struct ListNode* pslow = head;
while(pfast && pfast->next)
{
pfast = pfast->next->next;
pslow = pslow->next;
if(pfast == pslow) // 有环
{
struct ListNode* phead = head;
while(phead != pslow) //使 两个指针 分别从 首节点和相遇点 一次一步 移动,直到相遇
{
phead = phead->next;
pslow = pslow->next;
}
return phead;
}
}
return NULL;
}

🌈
本篇是对 单链表带环问题 的一个深入探索,单链表带环问题是 单链表中一个非常重要的应用 和 对单链表非常重要的理解。同时,他已经进入了大厂面试可能会考的范畴,重要的是对 单链表带环问题的深入分析 ,而不是简单的判断是否有环。
本篇文章到此结束
感谢阅读~

求评论、点赞、收藏~
博主其他文章推荐:
🌈【神秘海域】[动图] 掌握 单链表 只需要这篇文章~ 「超详细」
🌈【程序员的自我修养】[动态图文] 理解编译到链接的过程 [编译与链接一]
🌈【程序员的自我修养】[动态图文] 超详解函数栈帧有兴趣的可以关注一下博主的专栏:
⭐专栏:神秘海域:数据结构与算法
⭐专栏:《程序员的自我修养》
ChatGPT是一款引人注目的产品,它的突破性功能在各个领域都创造了巨大的需求。仅在发布后的两个月内,就累计了超过1亿的用户。它最突出的功能是能够在几秒钟内完成各种文案创作,包括论文、歌曲、诗歌、睡前故事和散文等。与流行的观点相反,ChatGPT可以做的不仅仅是为你写一篇文章,更有用的是它如何帮助指导您的写作过程和写作方法。接下来手把手教你利用ChatGPT辅助完成写作的五种方法。1.使用ChatGPT生成论文的观点在开始写作之前,我们需要让ChatGPT帮我们充实想法,找到论文切入点。当老师布置论文时,通常会给予学生一个提示,让他们可以自由地表达和分析。这时,我们需要找到论文的角度和思路,然
目录1 例题1.1 卡片换位1.2 人物相关性分析2 字符串的读取2.1 综述2.2 scanf2.3 getline/getchar/get2.4 注意2.5 说明3 C语言中字符串有关问题3.1 常用函数3.2 使用实例3.3 附一些函数先看例题1 例题1.1 卡片换位问题描述你玩过华容道的游戏吗?这是个类似的,但更简单的游戏。看下面3x2的格子在其中放5张牌,其中A代表关羽,B代表张飞,*代表士兵。还有一个格子是空着的。你可以把一张牌移动到相邻的空格中去(对角不算相邻)。游戏的目标是:关羽和张飞交换位置,其它的牌随便在哪里都可以。输入格式:输入两行6个字符表示当前的局面输出格式:一个整
UART串口这个东西,是嵌入式学习上避不开的,不仅在调试中经常用到,还有很多模块通过串口与SOC相连。这篇文章让你彻彻底底,搞明白串口程序的编写。没有基础的先看:嵌入式Linux学习系列全部文章:嵌入式Linux学习—从裸机到应用教程大全 目录1.UART串口1.1UART硬件连接1.2UART软件通信协议2.读手册,编程序2.1找对应引脚2.2设置GPIO为UART功能2.3设置UART(初始化)2.4编写发送接收函数3.完整代码和验证1.UART串口全称:通用异步收发传输器(UniversalAsynchronousReceiver/Transmitter,简称UART)是一种串行异步收发
SpringCloudAlibaba全集文章目录:零、手把手教你搭建SpringCloudAlibaba项目一、手把手教你搭建SpringCloudAlibaba之生产者与消费者二、手把手教你搭建SpringCloudAlibaba之Nacos服务注册中心三、手把手教你搭建SpringCloudAlibaba之Nacos服务配置中心四、手把手教你搭建SpringCloudAlibaba之Nacos服务集群配置五、手把手教你搭建SpringCloudAlibaba之Nacos服务持久化配置六、手把手教你搭建SpringCloudAlibaba之Sentinel实现流量实时监控七、手把手教你搭
我有一个Rails项目,其中一个常量在处理请求时在某个时刻被破坏。我正在使用mime/types和restclientgem。restclient模块定义了MIME的扩展,其中包含type_for_extension方法。moduleRestClient...defstringify_headersheadersresult[key]=target_values.map{|ext|MIME::Types.type_for_extension(ext.to_s.strip)}.join(',')...endendendmoduleMIMEclassTypesdeftype_for_ext
我非常困惑:这几乎是从RoR操作邮件程序指南中复制/粘贴的,但它会引发语法错误:classContacta_name,:company=>a_company,:phone=>a_phone,:email=>a_email,:comments=>a_comments}endend错误是:app/models/contact.rb:9:syntaxerror,unexpectedtASSOC,expecting'}'body{:name=>a_name,:company=>a_company...^app/models/contact.rb:9:syntaxerror,unexpected
今天,我无意中发现了Ruby中神秘的Data类,但我找不到任何有用的信息来说明它的作用或它为什么存在。我假设它是语言实现本身的一部分。有人知道它的作用吗?mbp-scott:~scott$irbruby-1.9.3-p0:001>Data=>Dataruby-1.9.3-p0:002>Data.is_a?Module=>trueruby-1.9.3-p0:003>Data.is_a?Class=>trueruby-1.9.3-p0:004>Data.ancestors=>[Data,Object,Kernel,BasicObject]ruby-1.9.3-p0:005>Data.met
当我的应用启动时,情节板启动屏幕显示我的图像如预期的,但部分被灰色盒子覆盖。有人可以让我知道图像框的来源吗?启动屏幕上唯一的东西是页面上的图像。这是屏幕截图:看答案您是否检查了启动图像是否损坏了?
原题链接https://www.dotcpp.com/oj/problem3162.html想直接看题解的,跳转到第三次尝试即可。已AC。解析:(1)首先大家要知道什么叫互质:以及它们的性质:欧拉函数在数论中,对正整数n,欧拉函数φ(n)是小于或等于n的正整数中与n互质的数的数目。此函数以其首名研究者欧拉命名,它又称为φ函数(由高斯所命名)或是欧拉总计函数(totientfunction,由西尔维斯特所命名)。例如φ(8)=4,因为1,3,5,7均和8互质。也可以从简化剩余系的角度来解释,简化剩余系(reducedresiduesystem)也称既约剩余系或缩系,是m的完全剩余系中与m互素的数
我将如何使用Javascript或JQuery剖析链接/href?我可以使用split来拆分一些变量,但我想知道是否有更简单的方法来解决这个问题,例如......www.url.com/dir/page?setting&var1=value1获取目录、页面和设置的最简单方法是什么。附言总是选择最后一个目录会很好,因此如果有多个目录,使用标准拆分并不总是有效。 最佳答案 我推荐JamesPadolsey的URL解析器——这是一个简单的JS函数,可以为您提供URL的任何部分(主机、查询字符串、路径等)http://james.padol