环形链表问题(判环+寻找入环点)

文章目录

  • 题目1.判断链表中是否有环
    • 1.1 思路分析(快慢指针)
    • 1.2 思考:为什么快指针每次走两步,慢指针每次走一步两者一定可以相遇?
    • 1.3 快指针一次走3步,走4步,...n步行吗?
  • 题目2. 寻找入环点
    • 2.1 思路1
    • 2.2 代码实现
    • 2.3 证明:为什么一个指针从相遇点开始走,一个指针从链表起始位置走,两者会在入环点相遇?
    • 2.4 思路2(转换为链表相交问题)
    • 2.5 代码实现

这篇文章,我们来看两道环形链表相关的题目。

题目1.判断链表中是否有环

链接: link
在这里插入图片描述
给你一个链表的头节点 head ,判断链表中是否有环。如果链表中存在环 ,则返回 true 。 否则,返回 false 。

1.1 思路分析(快慢指针)

什么是环形链表呢?

其实就是链表的尾结点的next不指向NULL,而是指向链表中的任意一个结点,也可以是自己。

我们来简化一下环形链表的图,其实就是长这样:
在这里插入图片描述

那这道题单纯的要判断链表中是否存在环其实很简单,思路就可以用我们熟悉的快慢指针:

定义两个指针fast和slow,初始都指向链表的第一个结点。然后快指针fast一次走两步,慢指针slow一次走一步
如果链表中存在环,那他们就一定会相遇,即fast==slow
如果链表中没有环,那么fast指针一定会率先走到空,因为fast是一次走两步,所以要考虑链表的可能是奇数个也可能是偶数个。
如果是奇数个循环结束条件是fast->next==NULL
偶数个的话就是fast==NULL时结束

那我们来写一下代码:

在这里插入图片描述
在这里插入图片描述
就过啦!

bool hasCycle(struct ListNode *head) {
    struct ListNode* slow,*fast;
    fast=slow=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(slow==fast)
            return true;
    }
    return false;
}

代码呢确实很简单,但是,还有一些问题值得我们来思考一下

1.2 思考:为什么快指针每次走两步,慢指针每次走一步两者一定可以相遇?

大家有没有想过为什么快指针每次走两步,慢指针每次走一步在带环的情况下两者一定可以相遇呢?

我们来一起证明一下:

慢指针slow一次走一步,我们假设慢指针进环的时候,fast和slow的距离是N
在这里插入图片描述
那此时fast和slow两个人都在环里,两者距离为N,而fast又比slow走得快,所以fast是不是就有可能追上slow。

那为什么fast一次走两步,slow一次走一步就一定可以追上呢?两者就一定会相遇呢?有没有可能会错过呢?

🆗,这样是一定可以相遇的:
此时两者都在环里,距离为N,fast一次走两步,slow一次走一步,所以它们的速度差是1。
也就是说,往后每走一次,两者的距离就缩小1
N,N-1,N-2,... ,3,2,1,0
那么N次之后,两者的距离就会缩小到0,此时两者就相遇了。
在这里插入图片描述
而且肯定在一圈之内就追上了,因为慢指针入环的时候,两者的距离肯定是小于环的周长的。

1.3 快指针一次走3步,走4步,…n步行吗?

那我们再来思考,上面我们证明了慢指针一次走一步,快指针一次走两步一定可以相遇。那么,快指针一次多走几步还可以吗?走3步,走4步,…n步行吗?

那这样的话能不能相遇就要看情况了,我们来分析一下,比如,我们以快指针每次走3步来分析一下(其它情况也类似):

慢指针slow呢还是一次走一步,那我们还是假设当slow走到入环点的时候,两者距离为N
slow进入环之后呢,fast就开始追击slow了。
那么此时fast一次走3步,slow一次走1步,即它们的速度差是2,也就是说,每追击一次,两者的距离缩小2
那此时它们还一定会相遇吗?
🆗,此时就要分情况看了:
如果N是偶数,那么N每次-2,最终一定可以减小到0,那就可以相遇。
如果N是奇数,每次-2,最终会减到...3,1,-1
那当它们的距离N变成-1的时候,意味着什么?
两者是不是错过了啊。fast直接跳到了slow前面距离为1的位置。
那此时两者的距离又变成了多少?
在这里插入图片描述
如果假设环的周长是C,那他们的距离就变成了C-1,然后fast重新开始追击slow,那这次能相遇吗?
是不是又取决于它们的新距离C-1是奇数还是偶数啊?
每次追击距离缩小2,如果C-1是偶数可以相遇如果C-1是奇数那么将永远追不上了!
因为C-1是奇数的话,最终又会减到-1,减到-1的话它们的距离就还是C-1,C-1是奇数,最终又会减到-1,减到-1的话它们的距离就还是C-1…
就会一直循环下去,永远追不上!!!
在这里插入图片描述
而C-1到底是奇数还是偶数,我们不知道,这取决与环的大小。

那如果每次fast走的更多,走4步,5步,…n步也是一样的:

就看它们在对应的速度差下距离能不能缩小到0,slow入环时距离为N,假设速度差是gap,N每次减去gap,如果最终可以减到0,就可以相遇(即看N是不是gap的整数倍)。
如果最终不能减到0,那他们就会错过,假设错过之后距离为C-X,如果C-X是速度差gap的整数倍,那还可以相遇,如果不是,那就永远不能相遇。

所以:

如果快慢指针的速度差是1,那么一定可以追上相遇,如果大于1,就不一定了。

题目2. 寻找入环点

那么下面我们再来看一道环形链表的题目
链接: link
在这里插入图片描述
这道题呢,我们不仅要判断链表有没有环,还要返回入环的结点,如果链表无环,则返回 null。

2.1 思路1

这道题单要写代码的话呢其实很简单,有一个方法是这样的:

上面我们刚做了一道题不是判断链表是否带环嘛,用快慢指针如果最终可以相遇的话就是有环。
那现在要寻找入环点,就可以这样:
让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终就一定会在入环点相遇。

2.2 代码实现

那我们来写一下代码,看看能不能通过:

在这里插入图片描述
在这里插入图片描述
🆗,就过啦!

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

但是,为什么一个指针从判环的相遇点开始走,一个指针从链表起始位置走,就一定会在入环点相遇呢?

那下面我们来证明一下这个结论

2.3 证明:为什么一个指针从相遇点开始走,一个指针从链表起始位置走,两者会在入环点相遇?

那我们依然还是来画图分析一下:

在这里插入图片描述
我们假设链表起点到入环点的距离为L,入环点到相遇点的距离为N,那相遇点在往前走到入环点的距离就是C-N。
那么快慢指针在相遇的时候,所走的路程:
慢指针slow:L+N
ps:慢指针在环内走的距离不会超过一圈的,上一题我们分析了,慢指针入环时两者的距离肯定小于N,一圈之内就追上了。
快指针fast:L+k*C+N
解释:快慢指针相遇时,快指针fast已经绕环走了k圈了,k至少为1。因为fast先进入环,而且速度快,所以一定先独自经过相遇点M,而最终两者又在M相遇。所以fast至少绕环走了一整圈再+N走到相遇点。
即k至少为1,至于具体的大小还取决于环的大小,环长C相对于L越小,k就越大。

然后:

又因为快指针的速度是慢指针的2倍,所以:
相遇时快指针的路程是慢指针的2倍,即
L+k*C+N=2*(L+N)
k*C=L+N
所以:
L=k*C-N,即:
L=(k-1)*C+C-N
在这里插入图片描述
那我们再来看图:
L=(k-1)*C+C-N,然后我们上面的思路不是让两个指针分别从起点和相遇点开始走嘛
在这里插入图片描述
begin从起点开始走,meet从相遇点开始走,两人同步走,每次都是走一步。
begin走了L步走到入环点
meet就也走L步,L又等于(k-1)*C+C-N,即meet先绕环走k-1圈(k>=1),那meet从入环点开始走的,不论走几圈,只要是整圈,还停下来就还是在相遇点这个位置嘛,然后还要走一个C-N,而我们看图C-N刚好就是相遇点距离入环点的距离。
所以meet走了L((k-1)*C+C-N)步之后正好也走到了入环点

那么就得以证明:

一个指针从判环的相遇点开始走,一个指针从链表起始位置走(每次都走一步),两者正好会在入环点相遇。
在这里插入图片描述

2.4 思路2(转换为链表相交问题)

那么这道题呢我们再来提供另外一种解法:

就是把它转换成链表相交的问题,我们前面写过这道题——链接: link

怎么做呢?

首先还需要找到快慢指针的相遇点,然后从相遇点把环形链表断开——变成单链表
在这里插入图片描述
然后就变成了相交链表找交点的问题

2.5 代码实现

我们来写一下代码:

相交链表找交点的代码我就不写了,我们直接拷贝之前写的:
在这里插入图片描述
然后我们只需:
在这里插入图片描述
提交
在这里插入图片描述
过啦!

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode *curA=headA;
    struct ListNode *curB=headB;
    int lenA=0;
    int lenB=0;
    //找尾,先判断是否相交,不相交直接返回NULL,相交再找
    while(curA->next)
    {
        lenA++;
        curA=curA->next;
    }
    //计算准确长度应该这样写:while(curA)
    //这样while(curA->next)比实际长度小1,但是两个都小1,不影响差值
    //而这样写循环结束cur就是尾,可直接判断是否相交
    while(curB->next)
    {
        lenB++;
        curB=curB->next;
    }
    //尾不相等,则不相交
    if(curA!=curB)
        return NULL;
    //计算差值
    int gap=abs(lenA-lenB);
    //找出长的那一个
    struct ListNode *longlist=headA;
    struct ListNode *shortlist=headB;
    if(lenB>lenA)
    {
        longlist=headB;
        shortlist=headA;
    }
    //长表先走差值步
    while(gap--)
    {
        longlist=longlist->next;
    }
    //再一起走,比较找交点
    while(longlist!=shortlist)
    {
        longlist=longlist->next;
        shortlist=shortlist->next;
    }
    return longlist;
}

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode * fast=head;
    struct ListNode * slow=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow)
        {
            struct ListNode *otherhead=fast->next;
            fast->next=NULL;
            return getIntersectionNode(head,otherhead);
        }
    }
    return NULL;
}

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

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

相关文章

HDLbits 刷题 --Always nolatches

学习: Your circuit has one 16-bit input, and four outputs. Build this circuit that recognizes these four scancodes and asserts the correct output. To avoid creating latches, all outputs must be assigned a value in all possible conditions (See also always…

vue快速入门(三)差值表达式

注释很详细&#xff0c;直接上代码 上一篇 新增内容 插值表达式基本用法插值表达式常用公式 源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-wid…

css心跳动画

图标引入 <img class"icon" src"heart.svg" alt"" srcset""> CSS代码 <style>.icon {animation:bpm 1s linear,pulse 0.75s 1s linear infinite;}keyframes pulse {from,75%,to {transform: scale(1);}25% {transform:…

软件测评中心▏软件安全测试之渗透测试的步骤和作用简析

软件安全测试是保证软件系统的信息安全的重要手段之一&#xff0c;而在软件安全测试中&#xff0c;渗透测试无疑是一项关键的技术。 渗透测试是指通过模拟攻击的手段&#xff0c;对软件系统进行安全评估和控制。通过模拟真实的黑客攻击过程&#xff0c;渗透测试可以发现软件系…

点大商城V2版 2.5.7全开源版 全插件+百度+支付宝+QQ+头条+小程序端+uniapp开源端

点大商城V2是一款采用全新界面设计支持多端覆盖的小程序应用&#xff0c;支持H5、微信公众号、微信小程序、头条小程序、支付宝小程序、百度小程序&#xff0c;本程序是点大商城V2独立版&#xff0c;包含全部插件&#xff0c;代码全开源&#xff0c;并且有VUE全端代码。分销&am…

《自然资源领域数据安全管理办法》发布,要求使用商用密码进行保护

近日&#xff0c;自然资源部发布《自然资源领域数据安全管理办法》&#xff08;以下简称管理办法&#xff09;&#xff0c;旨在规范自然资源领域数据处理活动&#xff0c;加强数据安全管理&#xff0c;保障数据安全&#xff0c;促进数据开发利用&#xff0c;保护个人、组织的合…

SSM框架学习——JSP语法入门

JSP语法入门 前提 在前一节中我们已经写过JSP的代码了&#xff0c;这一节将单独介绍JSP一些基础语法。当然&#xff0c;你可以跳过这一节&#xff0c;当后面有代码不太理解的时候再回来阅读。 中文编码问题 如果中文乱码&#xff0c;看看JSP是否是以UTF8的方式编码&#xf…

算法知识点汇总

知识点 1. 求二进制中1的个数 int get_count(int x)//返回x的二进制有多少个1 int get_count(int x) {int res 0;while (x){res ;x - x & -x;}return res; }2. 建树&#xff0c;和树的DFS 记得初始化头节点 const int N 1e5 10, M N * 2; int h[N], e[M], ne[M], id…

VMware创建Ubuntu虚拟机详细教程

下载ISO映像文件 进入官网下载&#xff1a;Download Ubuntu Desktop | Download | Ubuntu 下面是一些其他的下载路径&#xff1a; 中国官网 https://cn.ubuntu.com/ 中科大源 Index of /ubuntu-releases/ (ustc.edu.cn) 阿里云开源镜像站 ubuntu-releases安装包下载_开源镜像…

[C++初阶]初识C++(一)—————命名空间和缺省函数

声明: 本篇文献内容选自百度文库、比特就业课 代码内容部分选自比特就业课 一、命名空间 1.什么是命名空间 在编程语言中&#xff0c;命名空间是一种特殊的作用域&#xff0c;它包含了处于该作用域中的所有标示符&#xff0c;而且其本身也是由标示符表示的。命名空间的使用目…

This app has no Android key hashes configured. . Configure your app key

Unity 接入 Facebook SDK 的过程中遇到这个问题&#xff0c;查了很多帖子&#xff0c;不太直观&#xff0c;记录下来方便需要的同学参考 报上面错误的原因是在https://developers.facebook.com/apps/ 设置里没有填入有效的密钥 怎么填入这个密钥呢&#xff0c;其实很简单&…

Linux驱动学习:从Linux主机nfs共享文件到uboot

第一步&#xff1a;在Linux主机上开启NFS服务&#xff0c;使用如下命令安装NFS服务&#xff1a; sudo apt-get install nfs-kernel-server rpcbind 第二步&#xff1a;创建一个文件夹用于共享&#xff0c;直接以nfs命名就行&#xff1a; 第三步&#xff1a;打开nfs服务配置文…

基于springboot实现校园周边美食探索及分享平台系统项目【项目源码+论文说明】

基于springboot实现园周边美食探索及分享平台系统演示 摘要 美食一直是与人们日常生活息息相关的产业。传统的电话订餐或者到店消费已经不能适应市场发展的需求。随着网络的迅速崛起&#xff0c;互联网日益成为提供信息的最佳俱渠道和逐步走向传统的流通领域&#xff0c;传统的…

找单链表倒数第K个节点

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 但行前路&#xff0c;不负韶华&#…

MTU/TCPMSS/VLAN/ACCESS/TRUNK/HYBRID

MTU RFC标准定义以太网的默认MTU值为1500 最小64字节是为了保证最极端的冲突能被检测到&#xff0c;64字节是能被检测到的最小值&#xff1b;最大不超过1518字节是为了防止过长的帧传输时间过长而占用共享链路太长时间导致其他业务阻塞。所以规定以太网帧大小为64~1518字节&am…

动态代理 过渡 AOP

提问&#xff1a;你可以按照网上教程写一个动态代理的案例&#xff1b;也可以写一个AOP的案例。也常听到AOP的底层就是动态代理&#xff0c;是否能解释的清楚它两之间的关系呢&#xff1f; 目录 一 无代理二、静态代理改造三 动态代理改造四 spring AOP的实现总结 一 无代理 p…

redis之主从复制、哨兵模式

一 redis群集有三种模式 主从复制&#xff1a; 主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。 主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的负载均衡和简单的故障恢复。 缺陷&#xff1a; 故障恢复无法自动化&…

Python搭建编程环境—安装Python3解释器

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;系列专栏&#xff1a;零基础学Python &#x1f4ac;个人格言&#xff1a;不断的翻越一…

数据结构(二)----线性表(顺序表,链表)

目录 1.线性表的概念 2.线性表的基本操作 3.存储线性表的方式 &#xff08;1&#xff09;顺序表 •顺序表的概念 •顺序表的实现 静态分配&#xff1a; 动态分配&#xff1a; 顺序表的插入&#xff1a; 顺序表的删除&#xff1a; 顺序表的按位查找&#xff1a; 顺序…

搭建跨境电商电商独立站如何接入1688平台API接口|通过1688API接口采集商品通过链接搜索商品下单

接口设计|接口接入 对于mall项目中商品模块的接口设计&#xff0c;大家可以参考项目的Swagger接口文档&#xff0c;以Pms开头的接口就是商品模块对应的接口。 参数说明 通用参数说明 参数不要乱传&#xff0c;否则不管成功失败都会扣费url说明……d.cn/平台/API类型/ 平台&…