经典面试题---环形链表

news2024/7/2 3:53:14

1. 环形链表1. - 力扣(LeetCode)

 

要解决这道题,我们首先要挖掘出带环的链表与不带环的链表之间的差别。

以此,才能设计出算法来体现这种差别并判断。

二者最突出的不同,就是不带环的链表有尾结点,也就是说在访问到某个结点时,其next指针可能为NULL。

而带环的链表则没有尾结点,负责访问链表的指针进入环以后,就会一直在环中转圈。

 但是,仅靠这个,我们很难解决这个问题。

按以上得到的信息来说,我们会尝试去1. 检查该链表是否有尾结点,或者2. 检查有无结点被重复访问

对于第一种思路,很明显行不通,因为在检查一个带环的链表时,我们会一直访问不到尾结点,但我们也无法找到确切的指标来证明该链表确实没有尾结点。

对于第二种思路,一般来说,我们会想到将已访问过的结点的地址保存起来,每访问一个结点,我们就检查已保存的地址中是否有与新结点地址相同的地址。

第二种思路没有任何问题,确实可以顺利解决这个问题,那么我们来看看进阶要求。

很明显,第二种思路写出的算法的时间复杂度为O(n!),空间复杂度为O(n)。

也就是说还有更妙的办法,或者说带环链表和不带环链表之间还有更加值得利用的区别。

仔细观察就能发现,带环会改变环中结点之间的相对关系。

当链表不带环时,我们可以肯定地说:值为2的结点在值为-4的结点之前。

但是,当链表带环时,我们却可以认为值为-4的结点在值为2的结点之前,因为值为-4的结点的next指针指向的是值为2的结点。

也就是说,带环改变了环中结点之间的前后相对关系。

那么,我们如何使得这种差异体现出来呢?

这使得我们想到快慢指针

假设快慢指针的都从头结点开始移动。

当链表不带环时,快慢指针一旦分开便再无相遇的可能;

当链表带环时,由于环中结点的相对关系被改变,在快慢指针都进入环之后,原本在前的快指针可以认为是在慢指针之后,那么快指针就会不断追击慢指针,最终二者可能相遇。

但是,我们如何确保快指针最后一定会追上慢指针呢?

在追击过程中,二者每次移动,它们之间的距离的减少量就是二者的速度之差。

由于在慢指针刚进入环时,二者之间的距离一定为整数,所以当二者的速度之差为1时,它们就必定相遇。

依据此思路,我们可以设计出如下的算法:

typedef struct ListNode ListNode;

bool hasCycle(struct ListNode *head) {
    ListNode* slow = head;
    ListNode* fast = head;

    while(fast && (fast->next))
    {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        return true;
    }
    return false;
}

很明显,该算法的时间复杂度为O(n),空间复杂度只有O(1),满足的进阶的要求并明显减小了时间复杂度。

2. 数学分析

完成这道题还不足以让你通过面试,接下来面试官可能会问你:

1. 二者为什么一定会相遇呢?

2. 慢指针每次前进一个结点,快指针每次前进三个结点可以吗?四或五个结点呢?

 第一个问题我们在解题的过程中就已经解决,你只要将思路清晰地阐述即可。

对于第二个问题,要让面试官刮目相看,我们自然不满足于只解决给出的三种情况(对具体情况单独分析也挺麻烦的),我们会尝试给出判断可行的数学公式。

我们假设:

1. 环的周长为c(环有c个结点)。

2. 当慢指针刚进入环时,快指针已经在环中移动的长度为L。

3. 快指针的速度为k(每次移动k个结点)。

4. 慢指针进入环之后,移动n次时与快指针相遇。

 将进入环的第一个结点标记为下标0,随后结点的下标依次递增,直到再次遇到下标为零的结点。

 二者相遇时,快慢指针的位置是相同的。

据此,当二者相遇时,一定满足这个表达式:(nk + L) % c = n % c

假设相遇时,快指针比慢指针多走了m圈,则有:nk + L = n + mc

移项可得:n = (mc - L) / (k - 1)

由于n为整数,所以,如果存在m使得(mc - L) % (k - 1) = 0,我们就可以认为二者一定会相遇。

很明显,当k = 2时,上式一定成立(任何整数都可以整除1),这也就验证了我们之前的结论。

而当k > 2时,上式能否成立则受到c与L的影响,无法保证一定成立。

3. 环形链表2. - 力扣(LeetCode)

 也就是在刚才的基础之上,要求我们求出环中的第一个结点。

这个时候,我们之前想到的第一种思路似乎就能派上用场了:如果找到了重复访问的结点,直接返回该结点的地址即可。

但是,这道题依然有同样的进阶要求:

同样的要求,也就是在提示我们这道题的最佳思路一定是建立在第一题的解法之上。

那么在第一题判断该链表有环之后,我们要如何找到环中第一个结点呢?

不妨先考虑一下,当快慢指针相遇时,二者在哪个位置。

当慢指针刚好进入环时,快指针的坐标为L % c。

按照快指针追击慢指针的角度来看,快慢指针之间相距的距离为c - (L % c)。

每移动一次,二者之间的距离会缩短1,那么二者共会移动c - (L % c)次。

所以,最终慢指针的坐标会停留在c - (L % c)处,也即二者相遇时的坐标。

此时,慢指针要再次到达下标为0的结点(环中第一个结点),需要再移动(L % c)次。

注意到,L = (L % c) + xc(x为整数)。

也就是说,让慢指针移动L次,其也依然会到达下标为0处(多绕x圈)。

可是,此时,我们怎么知道L是多少呢?

仔细观察会发现,头结点到环中第一个结点的距离恰好就是L。

因为快指针的速度是慢指针的两倍,所以,在进环之前,头结点到慢指针的距离与慢指针到快指针的距离是相等的。

在慢指针恰好进入环时,头结点到环中第一个结点的距离就等于慢指针到快指针的距离,即L。

所以,我们只需要让头结点处的指针和相遇处的指针同时开始移动(每次一个结点),最终就会在下标为0的结点处相遇。

依据这个思路,我们可以写出如下代码:

typedef struct ListNode ListNode;

struct ListNode *detectCycle(struct ListNode *head) {
    ListNode* slow = head;
    ListNode* fast = head;

    while(fast && (fast->next))
    {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        {
            ListNode* meet = slow;
            while(meet != head)
            {
                meet = meet->next;
                head = head->next;
            }
            return meet;
        }
    }
    return NULL;
}

4. 结语

如果你在面试中遇到了这道题并清晰流畅地答出了本文的内容,那么你的面试就稳了一半了。

如果觉得写的不错就三连支持一下吧!

加纳……

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

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

相关文章

Springboot打包jar如何后台启动和查看日志?

如何后台启动Spring Boot的fat jar 使用nohup命令启动: 在Linux或Unix系统中,你可以使用nohup命令来启动jar包,以确保即使你关闭了终端或断开了SSH连接,程序仍然可以在后台运行。命令格式如下:nohup java -jar yourapp…

C语言(指针)6

Hi~!这里是奋斗的小羊,很荣幸各位能阅读我的文章,诚请评论指点,关注收藏,欢迎欢迎~~ 💥个人主页:小羊在奋斗 💥所属专栏:C语言 本系列文章为个人学习笔记&#x…

opencv车道偏离系统-代码+原理-人工智能-自动驾驶

车道偏离预警系统(Lane Departure Warning System, LDWS)是一种主动安全技术,旨在帮助驾驶员避免因无意中偏离车道而引发的事故。从原理到实战应用,其工作流程大致如下: 传感器采集 :系统通常配备有一个或…

智能终端RK3568主板在智慧公交条形屏项目的应用,支持鸿蒙,支持全国产化

基于AIoT-3568A的智慧公交条形屏,可支持公交线路动态展示,语音到站提醒,减少过乘、漏乘的情况,有效提高了公交服务效率和质量,为乘客提供了更舒适、更安全和更方便的出行体验,为城市的发展增添了新的活力。…

升级Microsoft 365后,SAP GUI中无法打开Excel的解决方案

最近,我们遇到了一个棘手的问题,一位客户在升级到Microsoft 365后,无法在SAP GUI中打开Excel。这个问题不仅影响了工作效率,也给用户的日常操作带来了不便。在本文中,我们将探讨问题的成因,并提供一种解决方…

纯福利|手把手教你如何白嫖免费的GPU资源(二)

大家好,我是无界生长。 前段时间写过一篇文章《纯福利|手把手教你如何白嫖免费的GPU资源(一)》,使用Google Colab提供的免费的GPU资源,今天接着写白嫖GPU资源攻略,可获得“长期免费的CPU实例资源…

Redis:分布式系统

文章目录 认识RedisRedis和MySQLRedis的场景Redis的设计 分布式单机架构应用数据分离架构应用服务集群架构 认识Redis 在开始Redis学习前,要先认识一下Redis Redis的设计,是想要把它当做是一个数据库,一个缓存,或者说是一个消息…

AI时代的网络安全战:以智取胜,守护数字安宁

在数字化浪潮的推动下,我们的生活和工作日益离不开互联网。然而,随着人工智能(AI)技术的飞速发展,网络安全问题也日益凸显。美国联邦调查局(FBI)的一则警报如同一记重锤,敲响了我们对…

无尘室手套的选定标准

无尘室手套的选定标准主要包括以下几个方面: 材料选择: 考虑与应用的相容性,例如某些化学品可能对某些材料产生反应。是否存在乳胶过敏也是一个重要的考虑因素。常见的选择包括丁腈手套,它们通常比乳胶手套更耐用且化学稳定性更…

【爬虫】爬取股票历史K线数据写入数据库(三)

前几天有写过两篇: 【爬虫】爬取A股数据写入数据库(二) 【爬虫】爬取A股数据写入数据库(一) 现在继续完善,分析及爬取股票的历史K线数据通过ORM形式批量写入数据库。 2024/05,本文主要内容如下…

Vue的学习 —— <vue响应式基础>

目录 前言 正文 单文件组件 什么是单文件组件 单文件组件使用方法 数据绑定 什么是数据绑定 数据绑定的使用方法 响应式数据绑定 响应式数据绑定的使用方法 ref() 函数 reactive()函数 toRef()函数 toRefs()函数 案例练习 前言 Vue.js 以其高效的数据绑定和视图…

Nginx - location中的匹配规则和动态Proxy

文章目录 官网location 规则详解动态Proxy使用多个 if 指令指定不同的 proxy_pass根据参数选择不同的 proxy_pass 官网 https://nginx.org/en/docs/http/ngx_http_core_module.html#location location 规则详解 Nginx的location指令工作原理如下: 位置匹配&#…

【中级软件设计师】上午题3-数据结构(查漏补缺版)

上午题3-数据结构 0 前言1 时间、空间复杂度2 串2.1 串的模式匹配 3 矩阵4 图4.1 邻接矩阵和邻接表 5 查找6 哈希表、7 树7.1 B树 0 前言 因为我之前考研系统地学习过数据结构和操作系统&#xff0c;这两部分的笔记不完整 1 时间、空间复杂度 指数<阶乘<n次方阶 使用队…

Java入门基础学习笔记22——程序流程控制

程序流程控制&#xff1a;控制程序的执行顺序。 程序有哪些执行顺序&#xff1f; 顺序、分支和循环。 分支结构&#xff1a; if、switch 循环&#xff1a; for、while、do-while 顺序结构是程序中最简单最基本的流程控制&#xff0c;没有特定的语法结构&#xff0c;按照代码…

01-02-5

1、单链表中按位置查找 a.原理 通过传递的位置&#xff0c;返回该位置对应的地址&#xff0c;放到主函数定义的指针变量中。 我们认为位置从&#xff1a;有数据的节点开始计数 即如下结构&#xff1a; 查找位置&#xff0c;就是返回该位置对应的空间地址。 b.代码说明 Ⅰ…

深度剖析深度神经网络(DNN):原理、实现与应用

目录 引言 一、DNN基本原理 二、DNN核心算法原理 三、DNN具体操作步骤 四、代码演示 引言 在人工智能和机器学习的浪潮中&#xff0c;深度神经网络&#xff08;Deep Neural Network&#xff0c;简称DNN&#xff09;已经成为了一种非常重要的工具。DNN模仿人脑神经网络的结…

2023年数维杯国际大学生数学建模挑战赛A题复合直升机的建模与优化控制问题解题全过程论文及程序

2023年数维杯国际大学生数学建模挑战赛 A题 复合直升机的建模与优化控制问题 原题再现&#xff1a; 直升机具有垂直起降等飞行能力&#xff0c;广泛应用于侦察、运输等领域。传统直升机的配置导致旋翼叶片在高速飞行过程中受到冲击波的影响&#xff0c;难以稳定飞行。为了在保…

【轮转数组】力扣python

1.python切片 这里nums[:]代表列表 class Solution:def rotate(self, nums: List[int], k: int) -> None:nlen(nums)nums[:]nums[-k%n:]nums[:-k%n] 2.边pop边push 0代表插入的位置 class Solution:def rotate(self, nums: List[int], k: int) -> None:nlen(nums)fo…

[Linux] 入门指令详解

目录 ls指令 pwd指令 whoami指令 cd指令 clear指令 touch指令 mkdir指令 rmdir指令 rm指令 man指令 cp指令 mv指令 cat指令 tac指令 more指令 less指令 head指令 tail指令 如何读取文件中间某一段内容&#xff1f; date指令 cal指令 find指令 which指令…

Java Web开篇

Java Web开篇 大纲 整个内容梳理 具体案例 整个内容梳理 这是前端和后端组成的系统的框架结构