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

news2024/10/11 8:23:10

文章目录

  • 题目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;
}

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

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

相关文章

一、企业级架构之LNMP

一、LNMP 概述 1、LNMP之间的关系: LNMP Linux Nginx MySQL PHP 2、配置LNMP服务器: (1) 克隆一台centos7虚拟机,修改 IP 地址 和 UUID 编号。 IP 为 10.1.1.10,UUID 修改后三位。 (2) 设置主机名称,绑定IP地…

机器学习周记(第三十二周:文献阅读-时空双通路框架)2024.3.25~2024.3.31

目录 摘要 ABSTRACT 1 论文信息 1.1 论文标题 1.2 论文摘要 1.3 论文模型 1.3.1 Spatial Encoder(空间编码器) 1.3.2 Temporal Encoder(时间编码器) 2 相关代码 摘要 本周阅读了一篇运用GNN进行时间序列预测的论文。论文…

挖一挖:PostgreSQL Java里的double类型存储到varchar精度丢失问题

前言 大概故事是这样的,PostgreSQL数据库,表结构: create table t1(a varchar);然后使用标准的Java jdbc去插入数据,其基本代码如下: import java.sql.*; public class PgDoubleTest {public static void main(Stri…

渐进式图片解决前端在页面中使用大图,图片体积过大导致页面出现白屏现象

1、演示 可以看到,图片还在拼命加载的时候, 页面上就已经有内容了 2、什么渐进式图片 图片一开始是模糊的,然后逐渐的开始变的清晰。如果页面上有一些大图,如果直接扔给浏览器的话那么图片的传输时间就会比较长,用户就…

Java对象Object对象头-MarkWord分析-hashCode

代码主要通过打印对象的内存布局来观察对象头在不同状态下的变化,进而分析对象头在不同情况下的内存布局情况。 System.out.println(ClassLayout.parseInstance(o).toPrintable());:这一行代码通过使用开源库 openjdk.jol 的 ClassLayout 类来解析对象 o…

算法思想堪比哲学,你知多少否?

对算法思想 - 分治算法的理解 分治算法是一种将复杂问题划分为规模较小的子问题,并递归地解决这些子问题,最后将它们的解合并为原问题的解的算法思想。 它具有以下几个关键步骤:分解、解决和合并。 通过将大问题分解为小问题,每个…

数据结构—堆

什么是堆 堆是一种特殊的树形结构,其中每个节点都有一个值。堆可以分为两种类型:最大堆和最小堆。在最大堆中,每个节点的值都大于等于其子节点的值;而在最小堆中,每个节点的值都小于等于其子节点的值。这种特性使得堆…

Linux实验过程

答案截图获取,代写: https://laowangall.oss-cn-beijing.aliyuncs.com/studentall.pdf 基本任务: 1.Linux操作系统安装 2.vi文本编辑 3. Linux用户及文件管理命令 4. Linux权限管理命令 5. Linux网络服务 提高任务: 1、Li…

vue3+elementPlus:实现数字滚动效果(用于大屏可视化)

自行封装注册一个公共组件 案例一&#xff1a; //成功案例&#xff1a; //NumberScroll.vue /* 数字滚动特效组件 NumberScroll */<template><span class"number-scroll-grow"><spanref"numberScroll":data-time"time"class&qu…

intellij idea 使用git的 cherry pick 摘取其他分支的comment

cherry pick 摘取其他分支的comment 如果想把 feature_v1.0 分支的comment 摘到 feature_v1.0_new 分支上&#xff0c; 先切换到 feature_v1.0_new分支&#xff0c;这一步不能少了。然后点击 下面菜单栏的 git&#xff0c;点击Local Changes旁边的 Log&#xff0c;这时能看到…

【Java】打包:JAR、EAR、WAR

打包&#xff1a;JAR、EAR、WAR war 是一个 Web 模块&#xff0c;其中需要包括 WEB-INF&#xff0c;是可以直接运行的 WEB 模块。而 jar 一般只是包括一些 class 文件&#xff0c;在声明了 main_class 之后是可以用 java 命令运行的。 它们都是压缩的包&#xff0c;拿 Tomcat …

SpringBoot登录校验(四)过滤器Filter

JWT令牌生成后&#xff0c;客户端发的请求头中会带有JWT令牌&#xff0c;服务端需要校验每个请求的令牌&#xff0c;如果在每个controller方法中添加校验模块&#xff0c;则十分复杂且冗余&#xff0c;所以引入统一拦截模块&#xff0c;将请求拦截下来并做校验&#xff0c;这块…

配置Pod使用PersistentVolume作为存储,PV类型为 hostPath

准备开始 在节点主机上创建一个 /mnt/data 目录&#xff1a; mkdir -p /mnt/data创建一个index.html文件 echo Hello from Kubernetes storage > /mnt/data/index.html创建PV 创建一个 hostPath 类型的 PersistentVolume。 Kubernetes 支持用于在单节点集群上开发和测试的…

第19次修改了可删除可持久保存的前端html备忘录:换了一个特别的倒计时时钟

第19次修改了可删除可持久保存的前端html备忘录:换了一个特别的倒计时时钟 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><met…

数据结构记录

之前记录的数据结构笔记&#xff0c;不过图片显示不了了 数据结构与算法(C版) 1、绪论 1.1、数据结构的研究内容 一般应用步骤&#xff1a;分析问题&#xff0c;提取操作对象&#xff0c;分析操作对象之间的关系&#xff0c;建立数学模型。 1.2、基本概念和术语 数据&…

glm2大语言模型服务环境搭建

一、模型介绍 ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本&#xff0c;在保留了初代模型对话流畅、部署门槛较低等众多优秀特性的基础之上&#xff0c;ChatGLM2-6B 引入了如下新特性&#xff1a; 更强大的性能&#xff1a;基于 ChatGLM 初代模型的开发经验&…

大数据实验三-HBase编程实践

目录 一&#xff0e;实验内容 二&#xff0e;实验目的 三&#xff0e;实验过程截图及说明 1、安装HBase 2、配置伪分布式模式&#xff1a; 3、使用hbase的shell命令来操作表&#xff1a; 4、使用hbase提供的javaAPI来编程实现类似操作&#xff1a; 5、实验总结及心得体会…

『VUE』10. 事件修饰符(详细图文注释)

目录 什么是事件修饰符?vuejs 不使用修饰符 原生js实现禁用事件对象的默认事件使用事件修饰符 .prevent使用事件修饰符 .stop使用事件修饰符 .self 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 什么是事件修饰符? vue 在 Vu…

『51单片机』蜂鸣器

&#x1f6a9; WRITE IN FRONT &#x1f6a9; &#x1f50e; 介绍&#xff1a;"謓泽"正在路上朝着"攻城狮"方向"前进四" &#x1f50e;&#x1f3c5; 荣誉&#xff1a;2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评…

【研发日记】白话解读UDS协议(一)——19 04读取快照服务

文章目录 前言 19服务 04子服务 19 04协议 快照存储设计 快照发送设计 功能验证 分析和应用 总结 前言 近期在一个嵌入式软件开发项目中&#xff0c;要按照UDS标准开发相关功能&#xff0c;期间在翻阅UDS标准时&#xff0c;周围同事都说很多地方晦涩难懂。所以利用晚上…