深度解读面试题:链表中环的入口结点(附代码,可过在线OJ)

news2024/11/24 2:32:59

在解读“链表中环的入口结点”前,我认为有必要明白关于它的一些用于打基础的问题(相交链表、判断链表中是否存在环)

相交链表

题目:

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。点击此处🤔前往该题
例如:
在这里插入图片描述

为了方便描述这类问题,我们给出更为抽象的逻辑图
在这里插入图片描述

首先我们第一眼能够想到的办法,就是遍历一遍然后一一进行比较。遍历的思路,以上图为例,将A中每个结点逐个和B中的结点进行比较,如果相等,那么该结点便是这两个链表的相交结点。(取部分讲解,A中的c1与B中的b1比较不相等,c1与b2比较不相等, c1与b3比较不相等,c1与c1比较相等!所以c1就是A和B的相交结点)。经过分析,可知这样的算法时间复杂度高达O(n2)。

通常的做法是
(1)获得两个链表的长度之差gap。
(2)长的链表从头开始,先走gap步
(3)长链表从第(2)步的位置开始,短的链表从头开始,一起往后走
(4)第一次遇到相等的结点了,该结点就是两个链表的相交结点
在这里插入图片描述
另外,在统计这两个链表各自的长度,获取差距步数的同时,可以判断这两个链表是否为相交的链表。判断的方式,两个链表的尾部结点(非空结点)相同,那么就是相交的。避免了两个链表根本不相交但依然要去执行上述逻辑,这样的无用功,算是进行了优化。还可以优化的细节,如果其中一个链表为空,那么他们就没有相交结点,直接返回一个空结点即可。代码实现如下:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if(headA == NULL || headB == NULL)
    return NULL;

    //1.获取两链表的差距步数
    int lenA = 1, lenB = 1;
    struct ListNode* tailA = headA, *tailB = headB;
    //1.1 获取A链表的长度
    while(tailA->next)
    {
        tailA = tailA->next;
        lenA++;
    }
    //1.2 获取B链表的长度
    while(tailB->next)
    {
        tailB = tailB->next;
        lenB++;
    }
    //如果两个链表根本不相交,就不要做无用功
    if(tailA != tailB)
    {
        return NULL;
    }
    int gap = abs(lenA - lenB);
    struct ListNode* longList = headA;
    struct ListNode* shortList = headB;
    if(lenA < lenB)
    {
        longList = headB;
        shortList = headA;
    }
    //2.长的链表先走差距步
    while(gap--)
    {
        longList = longList->next;
    }

    //3.短的链表从头开始,一起走
    while(longList)
    {
        //遇到相等的结点了,该结点就是相交结点
        if(longList == shortList)
        {
            return longList;
        }
        longList = longList->next;
        shortList = shortList->next;
    }
    return NULL;
}

判断链表中是否存在环

给你一个链表的头节点 head ,判断链表中是否有环。例如:
在这里插入图片描述
点击此处前往该类题, 为了方便描述这类问题,我们给出更为抽象一点的逻辑图
在这里插入图片描述
判断一个链表中存在环,更为普遍的办法是定义两个指针,一个指针一次走一步,另一指针一次走两步,前者我们称之为慢指针,后者称之为快指针。若存在环,那么快指针必定会追上慢指针;若不存在环,快指针必定会先走到链表尾部。

为什么慢指针一次走一步,快指针一次走两步,若存在环,快指针必定会追上慢指针的原理证明:
若存在环,当slow开始进环时,fast已经在环里面了。假设入环前的长度为L,那么当slow开始进环时,fast已经在环内走了L(slow走了L,fast就走了2L,fast入环前已经走了L,故在环内走了L)。下面的这张示意图,是环比入环前距离大的样例。也有可能slow进环前,fast已经在环里走了好几圈了!但要记住无论环大还是环小,fast在环内走了几圈,fast在环内所走的步数一定是L。
在这里插入图片描述
fast去追slow,slow开始进环时,假设它们之间的距离为N,要分清楚这里所指的距离到底是哪一段。
走一次:slow走了一步,fast走了两步。那么slow和fast之间的距离就减少了1,此时距离为N-1
走一次:slow走了一步,fast走了两步。那么slow和fast之间的距离就减少了1,此时距离为N-2
走一次:slow走了一步,fast走了两步。那么slow和fast之间的距离就减少了1,此时距离为N-3
… …
slow在环内走了N次后:此时距离为N-N = 0,fast追上了slow!

在这里插入图片描述
到这里的时候,你可能会产生疑问,为什么一定是慢指针一次走一步,快指针一次走两步。快指针一次走三步行不行?一起验证这样的假设是否可行。

不管fast在环内走了多少,假设slow开始进环时,fast和slow相距N
在这里插入图片描述
走一次:slow走了一步,fast走了三步。那么slow和fast之间的距离就减少了2,此时距离为N-2
走一次:slow走了一步,fast走了三步。那么slow和fast之间的距离就减少了2,此时距离为N-4
走一次:slow走了一步,fast走了三步。那么slow和fast之间的距离就减少了2,此时距离为N-6
… …

如果N是偶数,也就是slow开始进环时,两指针的差距是偶数,距离才会逐渐变为0;否则就有可能会错过

  • 以slow开始进环时,slow和fast距离N为偶数6举例:
    走一次:slow走了一步,fast走了三步。那么slow和fast之间的距离就减少了2,此时距离为4
    走一次:slow走了一步,fast走了三步。那么slow和fast之间的距离就减少了2,此时距离为2
    走一次:slow走了一步,fast走了三步。那么slow和fast之间的距离就减少了2,此时距离为0
    fast追上slow了,该链表存在环!

  • 以slow开始进环时,slow和fast距离N为奇数7举例:
    走一次:slow走了一步,fast走了三步。那么slow和fast之间的距离就减少了2,此时距离为5
    走一次:slow走了一步,fast走了三步。那么slow和fast之间的距离就减少了2,此时距离为3
    走一次:slow走了一步,fast走了三步。那么slow和fast之间的距离就减少了2,此时距离为1
    走一次:slow走了一步,fast走了三步。那么slow和fast之间的距离就减少了2,此时距离为-1
    距离变成了-1?实质上,两者的距离又开始反向变大了!这一次就错过了
    在这里插入图片描述
    当然,还可以让fast继续追slow,这一轮是否会错过,要取决于环的大小,这不就是上一步操作的轮询吗?C为环的大小,此时C-1就是两者之间的距离差N。如果C-1为偶数,那么fast就可以追上slow,从而证明该链表存在环;如果C-1依旧为奇数,那么就会永远错过,无法证明该链表是否存在环。

其他方案请自行证明。 综合而言,慢指针一次走一步、快指针一次走两步是判断链表中是否存在环的最优解决方案。参考代码如下:

bool hasCycle(struct ListNode *head) {
	//如果链表为空,一定不存在环
    if(head == NULL)
    {
        return false;
    }
    struct ListNode* slow = head, *fast = head;
    while(fast && fast->next)
    {
    	//慢指针一次走一步
    	//快指针一次走两步
        slow = slow->next;
        fast = fast->next->next;
        if(fast == slow)
        {
            return true;
        }
    }
    return false;
}

链表中环的入口结点

经过前两个类型的题,算是为这道题打下一些基础知识,
题目:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

思考“判断链表存在环”类题,并结合其中的两个图,你能联想到什么?
环比入环前长度大的情况:
在这里插入图片描述
入环前的长度、相遇点到入环结点的长度都是L。到这里我们就应该能想到两个链表相交的思想,如果你看不出来,我们可以将其中部分稍微拉平一点。
在这里插入图片描述
再使用“找相交链表的第一个相交结点”的办法,就能够找到找到这个入环结点了。

当然,这是环大小比入环前长度大的情况。环比较小的情况也是一样的,由于采用慢指针一次走一步、快指针一次走两步的策略,慢指针slow开始入环前,快指针fast已经在环内走了L步(不管它究竟在环内走了几圈)。所以环比较小时,fast走过几圈了的长度+快慢指针相遇点到入环结点的长度 = L。
绕过一个弯来说,不论环大还是环小,我们都只需要定义两个指针,一个指针从链表的头部开始走,一个指针从快慢指针相遇点走,都是一次走一步。如果有环,那么它们必定会相遇。相遇的点,便是入环结点
(1)环比较大时,快慢指针相遇点开始走的指针,不会走超过环的一圈。
(2)环比较小时,快慢指针相遇点开始走的指针,会走了环的很多圈。

综上所述,我们可以总结出求解入环结点的思路:
(1)判断链表中是否存在环,同时得到其中快慢指针相遇的结点;
(2)定义两个指针,一个指针从链表头部开始走,另一指针从快慢指针相遇点开始走;
(3)两个指针相遇的结点,就是入环结点。

//判断是否存在环,并且得到快慢指针相遇的结点
struct ListNode* hasCycle(struct ListNode* head){
    if(head == NULL)
    return NULL;

    struct ListNode *slow = head, *fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        //fast追上了slow,存在环
        if(slow == fast)
        {
            return fast;
        }
    }
    return NULL;
}
struct ListNode *detectCycle(struct ListNode *head) {
    //判断是否存在环,如果存在则获得快慢指针相遇点
    struct ListNode* meetNode = hasCycle(head);
    if(meetNode == NULL)
    {
        //不存在环
        return NULL;
    }

    //另外定义两个指针
    //一个指针从链表头开始走,一个指针从快慢指针相遇点开始走
    struct ListNode* ptr1 = head, *ptr2 = meetNode;
    while(ptr1 != ptr2)
    {
        ptr1 = ptr1->next;
        ptr2 = ptr2->next;
        //两个指针相遇了,相遇结点便是入环结点
        if(ptr1 == ptr2)
        {
            return ptr1;
        }
    }
    //如果能够到这里,说明第一个结点就是入环结点
    return head;
}

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

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

相关文章

快收藏!!整理了100个Python小技巧!!

下面&#xff0c;我就给大家分享100个Python小技巧&#xff0c;帮助大家更好的了解和学习Python&#xff0c;欢迎收藏、关注&#xff0c;点赞支持&#xff01; ▍1、for循环中的else条件 这是一个for-else方法&#xff0c;循环遍历列表时使用else语句。下面举个例子&#xff…

根据平均值列出记录

AANSI SQL包括几个聚合函数&#xff0c;使您可以对一组值进行计算以将其结果作为单个值返回。他们包括Count(), Min(), Max(), Sum() and AVG(),以及其他。默认情况下&#xff0c;聚合函数适用于所有行&#xff0c;但是您可以通过将WHERE子句应用于SELECT语句来缩小字段的范围。…

直冲云霄,阿里大牛耗时49天整理12W字面试手册,押题准确率直冲95%

很多人都想进字节做开发&#xff0c;不论是技术还是薪资、福利都算得上TOP级~ 7月底官方再次启动扩招&#xff0c;发布了1200&#xff0b;后端工程师岗位&#xff01; 那么本批有哪些优质岗位可选择&#xff1f;薪资待遇如何&#xff1f; 下面给大家列出几类具体的岗位要求&a…

软件项目管理指南:定义、5大过程、估算及进度管理方法等

本文将分享&#xff1a;1、软件项目管理的定义&#xff1b;2、软件项目管理的过程步骤&#xff1b;3、软件项目管理的内容&#xff1b;4、软件项目估算与进度管理方法&#xff1b;5、软件开发各生命周期阶段与文档、角色间的关系&#xff1b;6、软件开发项目中的各大角色职能&a…

深度学习-第P1周——实现mnist手写数字识别

深度学习-第P1周——实现mnist手写数字识别深度学习-第P1周——实现mnist手写数字识别一、前言二、我的环境三、前期工作1、导入依赖项并设置GPU2、导入数据集3、数据可视化四、构建简单的CNN网络五、训练模型1、设置超参数2、编写训练函数3、编写测试函数4、正式训练六、结果可…

ADSP-21489的图形化编程详解(7:延时、增益、分频、反馈、响度)

延时 21489 可以做延时&#xff0c;音频高手会运用此项算法来增强音效&#xff0c;我们做个最简单的&#xff0c;让大家知道怎么用它&#xff0c;至于怎么样嵌入到自己的系统里实现更好的效果&#xff0c;则需要各位调音师专业的耳朵来判断&#xff0c;调音无上限&#xff01;…

MySQL之索引及其背后的数据结构

✨博客主页: 荣 ✨系列专栏: MySQL ✨一句短话: 难在坚持,贵在坚持,成在坚持! 文章目录一. 索引的介绍1. 什么是索引2. 索引的使用二. 索引背后的数据结构1. 考虑使用哈希表2. 二叉搜索树3. N叉搜索树(B树, B树)4. 注意事项一. 索引的介绍 1. 什么是索引 索引 (Index) 是帮助…

[激光原理与应用-39]:《光电检测技术-6》- 光干涉的原理与基础

目录 第1章 概述 1.1 什么是光干涉 1.2 产生干涉的必要条件 1.3 非相干光 - 自发辐射无法产生干涉 1.4 相干光 - 受激辐射 1.5 时间相干性 1.6 空间相干性 它山之石 第1章 概述 1.1 什么是光干涉 它是指因两束光波相遇而引起光的强度重新分布的现象。 指两列或两列以上…

Verilog入门学习笔记:Verilog基础语法梳理

无论是学IC设计还是FPGA开发&#xff0c;Verilog都是最基本、最重要的必备技能。但任何一门编程语言的掌握都需要长期学习。并不是简简单单的随便读几本书&#xff0c;随便动动脑筋那么简单。Verilog是一门基于硬件的独特语言&#xff0c;由于它最终所实现的数字电路&#xff0…

基于AVDTP信令分析蓝牙音频启动流程

前言 公司项目edifier那边需要在原来音频SBC,AAC基础上增加LHDC5.0编码&#xff0c;在打通lhdc协议栈之前&#xff0c;学习记录一番AVDTP音频服务流程。 一、AVDTP音频流基础知识 分析音频流程首先应具备的最简单基础概念知识&#xff1a;AVDTP信令signal&#xff0c;流端点se…

【JVM】垃圾回收机制详解(GC)

目录一.GC的作用区域二.关于对象是否可回收1.可达性分析算法和引用计数算法2.四种引用类型三.垃圾收集算法1.标记-清除算法2.复制算法3.标记-整理算法4.分代收集算法四.轻GC(Minor GC)和重GC(Full GC)一.GC的作用区域 可以看jvm详解之后&#xff0c;再来理解这篇文章更好 堆和…

[附源码]计算机毕业设计农村人居环境治理监管系统Springboot程序

项目运行 环境配置&#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…

ASP.NET Core 3.1系列(18)——EFCore中执行原生SQL语句

1、前言 前一篇博客介绍了EFCore中常见的一些查询操作&#xff0c;使用Linq或Lambda结合实体类的操作相当方便。但在某些特殊情况下&#xff0c;我们仍旧需要使用原生SQL来获取数据。好在EFCore中提供了完整的方法支持原生SQL&#xff0c;下面开始介绍。 2、构建测试数据库 …

Radare2 框架介绍及使用

Radare2 框架介绍及使用 欢迎入群交流 radare2 这是整个框架的核心工具&#xff0c;它具有debugger和Hexeditor的核心功能&#xff0c;使您能够像打开普通的文件一样&#xff0c;打开许多输入/输出源&#xff0c;包括磁盘、网络连接、内核驱动和处于调试中的进程等。 它实现了…

旧版本金庸群侠传3D新Unity重置修复版入门-lua”脚本“

金庸3DUnity重置入门系列文章 金庸3dUnity重置入门 - lua 语法 金庸3dUnity重置入门 - UniTask插件 金庸3dUnity重置入门 - Cinemachine 动画 金庸3dUnity重置入门 - 大世界实现方案 金庸3dUnity重置入门 - 素材极限压缩 (部分可能放到付费博客&#xff09; 2022年底~20…

Apifox和Eolink两个测试工具谁最实用?

目前行业内有 postman、jmeter 为代表开源 Api 工具派系&#xff0c;我想对大家对这两个词并不陌生。虽然它们能解决基本的接口测试&#xff0c;但是无法解决接口链路上的所有问题&#xff0c;一个工具难以支持整个过程。在国内&#xff0c;我们可以看到有国产 API 管理工具&am…

Spring Cloud 微服务讲义

Spring Cloud 微服务讲义第一部分 微服务架构第 1 节 互联网应用架构演进第 2 节 微服务架构体现的思想及优缺点第 3 节 微服务架构中的核心概念第二部分 Spring Cloud 综述第 1 节 Spring Cloud 是什么第 2 节 Spring Cloud 解决什么问题第 3 节 Spring Cloud 架构3.1 Spring …

CCES软件做开发,如果仿真器连不进目标板怎么解决?(Failed to connect to processor)

ADI的DSP调试&#xff0c;我在Visual DSP软件下写过一个详细的帖子&#xff0c;来说明仿真器如果连不进目标板&#xff0c;可能存在的几种问题以及解决办法&#xff0c;现在在CCES软件下遇到了同样的问题&#xff0c;所以准备再写一个帖子说明一下。 我们都知道ADI的DSP&#…

智慧工地管理平台系统厂家哪家强|喜讯科技

喜讯科技针对施工现场涉及面广&#xff0c;多种元素交叉&#xff0c;状况较为复杂&#xff0c;如人员出入、机械运行、物料运输等工程项目管理在一定程度上存在着决策层看不清、管理层管不住、执行层做不好的问题。 围绕施工现场管理&#xff0c;构建全方位的智能监控防范体系弥…

Redis——Linux下安装以及命令操作

一、概述 redis是什么&#xff1f; Redis&#xff08;Remote Dictionary Server )&#xff0c;即远程字典服务 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 是一款高性能的NOSQL系列的非关系型…