LeetCode | 一探环形链表的奥秘【快慢双指针妙解BAT等大厂经典算法题】

news2025/1/17 6:03:31

前言

本文总结了力扣141.环形链表|以及142.环形链表||这两道有关环形链表的求解方案,去求证链表是否带环已经如何找出入环口的结点。
有关环形链表,在BAT等大厂面试中均有出现,一般是属于中等难度的题,需掌握

环形链表| && 环形链表||

  • 一、题目描述
  • 二、思路分析与罗列
  • 三、证明:
    • 1、【为何快指针每次走两步,慢指针走一步一定能相遇?】
    • 2、【快指针一次走3步,走4步,...n步行吗?】
  • 四、进阶:如何求出环的入口结点
    • 头结点到入口结点的距离剖析求证
  • 五、疑难解惑:为什么快指针会在慢指针进入环内的第一圈就相遇?
    • 原理图
  • 六、整体代码展示
    • 1、环形链表|
    • 2、环形链表||
  • 七、总结与提炼

一、题目描述

原题传送门

给你一个链表的头节点 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
解释:链表中没有环。

提示:

  • 链表中节点的数目范围是 [0, 104]
  • -105 <= Node.val <= 105
  • pos 为 -1 或者链表中的一个 有效索引 。

二、思路分析与罗列

好,看完了题目描述,接下去我们来分析一下如何去求解这道题目

  • 首先对于此题,首先你要考虑的一点是怎么去判断一个链表是否有环?
  • 在一开始看题目的时候你可能想了很多的办法,但是当写代码的时候,发现又不对。有点同学就和我说:这不是很简单,搞一个指针,做一个遍历,若是若是这个指针又回到原来的交点,那不就是带环吗
  • 那我只能说,这个同学没有读清楚题目,题目并没有告诉你这个环的入口在哪里,你怎么去判断这个遍历的指针走了一圈呢?所以这都是无稽之谈,我们应该通过画图来进行分析

在这里插入图片描述

  • 从上述图中可以看到,我使用来一个叫做快慢指针,这其实是解决环形链表这种问题的最好手段,具体的思路就是让快慢指针同时遍历这个链表,快指针走两步,慢指针走一步,然后在不断遍历的过程中,若是两个指针重合了,说明链表带环,具体的证明我放在后面讲解
  • 我们先来看一下快慢指针是如何遍历的

牢记规则:快指针走两步,慢指针走一步

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 好,通过上面的算法图示,相信你已经明白了快慢指针最后究竟是如何相遇的,这个光凭空想还真的不好想出来,但是我们画个图来分析一下,就非常地明确了。我们在下一模块来写写代码
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* fast, *slow;
        fast = slow = head;

        while(fast && fast->next)       //判断奇数和偶数个结点的情况
        {
            slow = slow->next;
            fast = fast->next->next;

            if(fast == slow)
                return true;
        }
        return false;
    }
};
  • 可以看出,代码并不复杂,就是通过一个循环去让这两个快慢指针去遍历这个链表,若是它们相遇,则【return true】,若是循环遍历结束还是没有遇见,则说明链表不带环

三、证明:

1、【为何快指针每次走两步,慢指针走一步一定能相遇?】

  • 相信在看完我上面的一些简略分析后有些小伙伴一定会疑惑为什么快指针每次走两步,慢指针每次走一步一定能相遇
  • 首先我在这里做一个假设,就是当快指针已经入环,而慢指针刚好入环时,他们之间的距离相差N

在这里插入图片描述

  • 然后此时缩小快慢指针的宏观移动距离,然后观察两个指针的移动的是否会改变他们之间的距离
  • 首先记录下它们的第一次移动①

在这里插入图片描述

  • 然后是第二次移动,继续计算它们之间的距离

在这里插入图片描述

  • 于是我们可以得出来下面这个结论,快指针【fast】和慢指针【slow】在不断前进的过程中它们之间的距离是会不断缩短的,当它们之间的距离 = 0时,其实也就意味着它们相交了
  • 其实快指针就是一个不断在追逐慢指针的一个过程

在这里插入图片描述

  • 所以就可以证明这个结论——》【快指针每次走两步,慢指针走一步一定能相遇】

2、【快指针一次走3步,走4步,…n步行吗?】

  • 接下去我们再来证明一个问题,刚才快指针一次是走两步,一定能追上,那现在当这个快指针一次走3步、走4步能不能追得上呢?我们一起来分析一下
  • 情况有很多,我这里就拿【fast】走3步,【slow】走1步来进行一个证明

在这里插入图片描述

  • 那根据我们上一个问题的证明,这依旧设【fast】在环中当【slow】刚进环时两者之间的距离为N,然后就可以得到两者在追击时它们之间的距离每次会缩短2,然后就可以去计算它们可不可能相遇
  • 因为它们之间的距离每次缩短的长度是一致的,所以就需要看这个N的大小,也就是在环中【fast】和【slow】之间的距离,若是N为偶数,那最后它们之间的距离一定会减少到0,也就意味着相遇;若是N为奇数,那最后它们之间的距离一定会减少到-1,这就意味着【fast】追是追上【slow】,但是呢却刚好错过了,到达了它的前一位

在这里插入图片描述

  • 我们来做一个模拟,此时当【fast】和【slow】快相遇时,它们继续行走

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

  • OK,可以看到,它们确实是错过了,那此时它们之间距离是多少呢?设整个环的周长为C。此时它们之间的距离就变成了【C-1】
  • 此时就需要在【C-1】的基础上再去考虑它们会不会相遇,那其实也是一样的道理,当【C-1】为偶数时,它们会相遇,当【C-1】为奇数时,它们之间的距离依旧会变回【C-1】,此时真的就变成一个环了,两个指针在里面绕来绕去就是不会相交,【fast】永远都追不上【slow】

在这里插入图片描述

  • 那这个问题的其他示例其实也是一样,比如说快指针每次走5步、走6步,慢指针走个2步、3步,其实都是一个道理,只不过要进行一个取余运算,最后的结果还是一样,只要他们之间的距离为奇数,那么就永远追不上

【最后我们可以得出结论:当两个指针的相对速度为1时,一定能相遇;当两个指针的相对速度> 1时,则需要视两个指针之间的距离而定】

四、进阶:如何求出环的入口结点

  • 此进阶为是环形链表|的后一道题👉142.环形链表||,可先看看原题

头结点到入口结点的距离剖析求证

  • 好,看完了如何去证明一些环内快慢指针相遇的问题,接下去我们继续深入,从我画的图里可看到,从链表的头结点过来有一个环,而且我标出了一个结点叫做【环的入口结点】,也就是那位同学说的从这个结点开始遍历去判断这个链表是否有环
  • 那我们该如何去求解这个环呢?这需要一个数学分析和推理验算的思维,听我给你讲一讲:📖
  • 首先我们做一个假设,从链表头结点开始到环形入口结点的距离为L,从入口结点到快慢指针相遇的距离为N,则从相遇处再到入口结点的距离就为C-N
  • 此时我们需要根据这些长度变量去写出慢指针和快指针到相遇为止走过的距离

在这里插入图片描述

  • 慢指针走过的路程根据我们上面的推论很好计算,就是【L + N】,而对于快指针来说,很多同学就会有所异或了,因为它是在追慢指针的一个过程,但是不知道它在经过了多少距离,于是有的同学就直接认为快指针走过的距离为【L + C + N】,也就是快指针在环中刚好走了一圈碰到慢指针,然后根据快慢指针的两倍关系,就得出L = C - N,其实这还是有所考虑不周,可能是我画的这个环误导他了,下面我在换一个环来看看

在这里插入图片描述

  • 可以看到,这环很小,我们设快指针走一步为半个环

在这里插入图片描述

  • 可以看到此时快指针已经走了一圈,要开始走第二圈

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

  • 然后经过一段时间后,他们终究会相遇,但此时快指针【fast】已经在环里走了好几圈了,因此这就是我们要考虑到的情况,当这个环很大的时候,可能环很大的时候,【fast】走上个一圈就可以遇到【slow】,但是当这个环很小的时候,【fast】就需要等待【slow】,于是它会在这个小环里一直走一直走,直到他们在环的入口点相遇为止
  • 然后我们就可以精确地分析出快指针所走的路程,即为【L + k*C + N】,k是快指针走了几圈,C是周长。于是我们就可以根据快慢指针的两倍关系得出从头结点到环形入口结点的距离L

在这里插入图片描述

  • 那其实这个快指针不是走了k圈,而是走了【k - 1】圈,因此我们可以将k用【k - 1】带入可得L = (k - 1)*C - N,为了和原本的等式相同,于是加上C,变为【L = (k - 1)*C + (C - N)
  • 此时我们就可以拿这个式子去分析了,当【k = 1】时,也就是快指针走了一圈时,L就等于【C - N】,那也就是我们一开始算的从快慢指针相遇处到环形入口结点之间的距离,当【k > 1】时,就需要另加考虑,让快指针先走上k圈,然后再用环的周长 - N,此时才是L的长度

  • 那这个时候有同学问了,求出这段L的长度有什么用呢?对于,有什么用。其实我就是在证明在快慢指针已经相遇后要如何行走才可以到达这个环的入口现在我们得出一个表达式为【L = (k - 1)*C + (C - N)

在这里插入图片描述

  • 那我们可以在快慢指针的相遇处定义一个指针【cur1】,在结点处再定义一个指针【cur2】,然后让他们一直走一直走,通过这个环的大小来看出【cur1】会在这个环里转多少圈。我们来看一下代码
ListNode *detectCycle(ListNode *head) {
    ListNode* slow, *fast;
    fast = slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            ListNode* cur1 = fast;
            ListNode* cur2 = head;
            while(cur1 != cur2)
            {
                cur1 = cur1->next;
                cur2 = cur2->next;
            }
            return cur1;
        }
    }
    return NULL;
}
  • 可以看出,代码并不难写,只是我们在分析证明的时候花了很大的心思

五、疑难解惑:为什么快指针会在慢指针进入环内的第一圈就相遇?

  • 有些小伙伴可能还是对快指针为何会在慢指针进到环里但是还没有碰到环的出口时就会相遇,我们来继续探究一下🔍

因为快指针一定是先进入环内的,然后慢指针才进到环内,然后当慢指针进入下一个入口时,快指针走的一定是慢指针的两倍,所以慢指针在没有进入到下一个入口处时,快指针在中间的某个位置一定和其相遇了

证明如下:

在快指针fast进入环口3时,它已经走了k + n个结点,从图中可以清晰地看出,k为快指针和慢指针之间的距离,n为一个环的距离,而慢指针在进入环内相应地走了(k + n)/2个结点,从图中可以看出k是小于n的,所以(k + n)/2也一样是小于n的,即慢指针在进入环内一圈不到的距离就会和快指针相遇

所以慢指针走动的距离为L + N就够了,其不会再走第二圈

原理图

请添加图片描述

六、整体代码展示

1、环形链表|

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* fast, *slow;
        fast = slow = head;

        while(fast && fast->next)       //判断奇数和偶数个结点的情况
        {
            slow = slow->next;
            fast = fast->next->next;

            if(fast == slow)
                return true;
        }
        return false;
    }
};

2、环形链表||

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow, *fast;
        fast = slow = head;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast)
            {
                ListNode* cur1 = fast;
                ListNode* cur2 = head;
                while(cur1 != cur2)
                {
                    cur1 = cur1->next;
                    cur2 = cur2->next;
                }
                return cur1;
            }
        }
        return NULL;
    }
};

七、总结与提炼

  • 最后我们来总结一下本文所介绍的内容,本文我们探究了链表章节比较复杂的一种题型——环形链表,这也是BAT等大厂在面试时很喜欢出的算法题,不仅仅是会让你手写代码,而且还会让你现场做个证明,因此对于上述的一些证明,希望大家也可以搞懂
  • 我们知道,编程的核心是算法,算法的本质是数学,你数学好了,逻辑思维就能强,面对一些棘手的推理算法题时才能游刃有余

以上就是本文所要描述的所有内容,感谢您对本文的观看,如有疑问请于评论区留言或者私信我都可以🍀

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

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

相关文章

教你vue-router命令视图应该怎么玩

引言 在VUE实战项目开发中&#xff0c;为了减少代码冗余&#xff0c;便于后期维护&#xff0c;我们经常会把相同布局的代码封装为公共组件&#xff0c;例如移动开发中NavBar导航栏、Tabbar标签栏等公共组件&#xff0c;需要使用时导入、注册、调用即可&#xff0c;但是相对NavB…

JSRPC的三种实现方式

RPC 为远程过程调用&#xff0c;本文通过在浏览器端&#xff08;服务端&#xff09;开启一个WebSocket服务&#xff0c;接收命令&#xff0c;执行浏览器网页的加密代码&#xff0c;得到密文。 CMD端&#xff08;客户端&#xff09;也开启一个WebSocket服务与浏览器端交互&#…

【Spring Boot】Day03

文章目录一、Value和ConfigurationProperties的区别二、PropertySource一、Value和ConfigurationProperties的区别 区别&#xff1a; 数据校验&#xff1a;判断数据是否合法 Value: 不支持数据校验ConfigurationProperties&#xff1a;支持数据校验 开启数据校验功能&#xf…

软考证书可积分落户、评职称、抵扣个税等,快来考一个吧!

很多人想要在工作的城市落户、买房、生活、小孩上学&#xff0c;但由于对于城市落户政策不了解&#xff0c;担心自己条件不够!今天给大家介绍一本软考证书帮你解决落户等问题。 软考&#xff0c;是由国家人力资源和社会保障部、工业和信息化部领导下的国家级考试&#xff1b;既…

[附源码]java毕业设计价格公示系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

C. Bouncing Ball(从后往前的前缀和)

Problem - 1415C - Codeforces 你正在为某个手机游戏创建一个游戏关卡。这个关卡应该包含一些从左到右排列的单元格&#xff0c;并以从1开始的连续整数编号&#xff0c;在每个单元格中&#xff0c;你可以放一个平台&#xff0c;也可以让它空着。 为了通过一个关卡&#xff0c;…

牛客网-《刷C语言百题》第四期

✅作者简介&#xff1a;嵌入式入坑者&#xff0c;与大家一起加油&#xff0c;希望文章能够帮助各位&#xff01;&#xff01;&#xff01;&#xff01; &#x1f4c3;个人主页&#xff1a;rivencode的个人主页 &#x1f525;系列专栏&#xff1a;《C语言入门必刷百题》 &#x…

QStyleFactor和QPalette

Qt中的分格都继承自QStyle&#xff0c;QStyle类是一个抽象基类&#xff0c;封装了一个GUI的外观。 QStyle常见的子类有&#xff1a; QStyleFactory类QPalette类QStyleFactor类 函数为&#xff1a; create()创建并返回与给定键匹配的QStyle对象keys()返回有效键的列表 获取有…

面试灵活拷问:对于数据库的索引,你是怎么理解的?

文章目录一、索引的概念及作用概念作用二、索引的应用场景三、索引的相关语法1.查询索引2.创建索引3.删除索引注意四、索引背后的数据结构什么是B树B树有什么特点采用B树结构能为索引带来什么好处五、索引的分类1.唯一索引&#xff08;unique键对应的字段&#xff09;2.主键索引…

Selenium基础 — 拓展:使用浏览器加载项配置实现用户免登陆

1、什么是加载项配置 在很多情况下&#xff0c;我们在登录网站的时候&#xff0c;浏览器都会弹出一个是否保存登录账号的信息。如果我们选择保存&#xff0c;那么我们在下次登录时就不用再次输入账号&#xff0c;直接免登录了。 在我们实际的测试过程中&#xff0c;测试注册登…

系统运维利器,百万服务器运维实战总结!一文了解最新版SysAK|龙蜥技术

在刚刚结束的龙蜥峰会 eBPF & Linux 稳定性专场上&#xff0c;龙蜥系统运维 SIG Maintainer 张毅做了《SysAK 系统运维工具集》的主题演讲&#xff0c;以下为演讲实录。 大家好&#xff0c;在去年的云栖大会&#xff0c;我们在龙蜥社区开源了系统运维工具集 SysAK&#xff…

new Vue的时候到底做了什么

Vue加载流程 1.初始化的第一阶段是Vue实例也就是vm对象创建前后&#xff1a;首先Vue进行生命周期&#xff0c;事件初始化发生在beforeCreate生命周期函数前&#xff0c;然后进行数据监测和数据代理的初始化&#xff0c;也就是创建vm对象的过程&#xff0c;当vm对象创建完成就可…

【Linux】gcc的使用

文章目录一、前言二、gcc的基本使用1. 预处理2. 编译3. 汇编4. 链接三、函数库四、gcc常用选项总结一、前言 在学习本文前&#xff0c;我们先简单回顾一下源代码被转换为可执行的机器指令的每个过程&#xff1a; 预处理&#xff08;进行宏替换)编译&#xff08;生成汇编)汇编…

windows什么录屏软件好用,windows屏幕录制软件

大部分人的电脑都是windows电脑&#xff0c;所以很多人都在找适合windows系统的录屏工具&#xff0c;windows什么录屏软件好用&#xff1f;我们到底该选择哪个录屏工具呢&#xff1f;今天我们就来给大家介绍windows版本的录屏工具。 一、易我录屏助手 这个工具很多人都比较熟悉…

艾美捷人重组MEGACD40L蛋白(可溶性)实例展示

艾美捷人重组MEGACD40L蛋白&#xff08;可溶性&#xff09;是一种高活性蛋白质&#xff0c;其中两个三聚体CD40配体分子通过脂联素/ACRP30/AdipoQ的胶原结构域人工连接。这种蛋白质非常有效地模拟体内CD40L的自然膜辅助聚集。 艾美捷人重组MEGACD40L蛋白&#xff08;可溶性&…

openEuler快速入门(二)-openEuler命令行基础操作

系列文章目录 第一章 openEuler快速入门(一)-openEuler操作系统介绍 文章目录系列文章目录前言一、shell是什么二、Linux命令行操作技巧三、基础命令3.1、Linux命令分类3.2、目录和文件3.2.1 相对路径和绝对路径3.2.2 处理目录的常用命令ls&#xff1a;cd&#xff1a;pwd&…

供应N3-PEG-COOH,Azide-PEG-acid,叠氮-聚乙二醇-羧基可增加溶解度

一&#xff1a;产品描述 1、名称 英文&#xff1a;Azide-PEG-acid&#xff0c;N3-PEG-COOH 中文&#xff1a;叠氮-聚乙二醇-羧基 2、CAS编号&#xff1a;N/A 3、所属分类&#xff1a;Azide PEG Carboxylic acid PEG 4、分子量&#xff1a;可定制&#xff0c;1000、2000、…

搭建Redis -Sentinel架构

准备服务器 master节点&#xff1a;192.168.1.121 从节点1&#xff1a;192.168.1.122 从节点2&#xff1a;192.168.1.123 安装详细步骤 1、下载安装包 https://redis.io/download/ 2、进入工作目录 $cd /home 3、解压安装包 $tar -zxvf redis-6.2.6.tar.gz 4、建立软连接 $l…

OpenCV_06 图像平滑:图像噪声+图像平滑+滤波

文章目录1 图像噪声1.1 椒盐噪声1.2 高斯噪声1.3 瑞利噪声1.4 伽马噪声1.5 指数噪声1.6 均匀噪声2 滤波器2.1 均值滤波器2.1.1 算数平均值滤波器2.1.2 几何均值滤波器2.1.3 谐波平均滤波器2.1.4 反谐波平均滤波器2.2 统计排序滤波器2.2.1 中值滤波器2.2.2 最大值滤波器2.2.3 最…

翻开spring源码横看竖看,满屏只有四个字,看不懂啊。幸好我有大神的深度剖析spring源码,轻松看懂

前言 有一天&#xff0c;我翻开源码横看竖看&#xff0c;满屏只看到四个字&#xff0c;我看不懂啊。 所以是不是曾和我一样迷失在毫无头绪的源码里&#xff0c;在各种类和方法里翻山越岭&#xff0c;却如同管中窥豹。是的话&#xff0c;要不今晚早点睡&#xff1f; 呸&#x…