LeetCode.141,142——环形链表,环形链表Ⅱ

LeetCode.141——环形链表:

题目如下:

通过题目中对于环形链表的大体描述,可以知道,环形链表最后一个结点保存了一个地址,用于返回链表中某个结点。并且。这个返回的结点并不是返回图中保存数据2的结点。而是返回链表中任意一个结点。即:


 

或者:

题目中给了两个要求,分别是:

1. 判断链表中是否有环

2. 如果不存在环,则返回false,存在环则返回true

对于不存在环的这种情况很好判断。如果链表中任意一个结点保存的地址为NULL,则这个链表不带环。但是难点在于如何判断链表带环。如果按照判断不带环的思想去判断是否带环,即链表是否可以无限运行下去显然不可能。

如果采用双指针的方法一个指针cur1从头结点开始,另一个指针cur2向后遍历,如果存在cur1->next == cur2-> next;则说明存在结点。否则就让cur1指向下一个结点的方法,也不能实现目的。因为带环链表按照上面的方法进行遍历时,因为带环链表中不存在存储NULL的结点。所以一旦开始遍历,也会造成死循环。

虽然上面给出的两种方法均不能解答题目。但是,第二种方法中,对比两个指针所指向的结点中保存的地址是否相等的思路是正确的。

在上一篇文章LeetCode——单链表相关题目(持续更新)_起床写代码啦!的博客-CSDN博客

中对于寻找链表中间结点时,用到了一个方法,及创建两个指针变量slow,fast,让 fast每次向后遍历2个结点,slow每次向后遍历1个结点。让两个指针变量所在链表中结点的位置形成一个差值。这个方法恰好可以用来解决本题。

为了方便后续进行画图演示,将环形链表用下方的图形进行表示:

其中head表示这个链表的头结点,start表示进入环的第一个结点。meet表示 slowfast这两个指针在环中相遇的结点。

将上面所提供的思路进行如下总结:

1.题目的两个要求中,返回false的情况是不存在环,也就是说链表中有某个结点存储的地址为NULL。对于返回true,判断条件就是slowfast这两个指针中存储的地址是同一个。所以,可以将解决题目的重点放在如何找到两个指针相遇的结点,也i就是meet

2.对于如何寻找meet,前面给出了方法,创建不同的指针,每个指针每次向后遍历的结点数是不同的。如果遇到两个指针中存储的地址相等。则说明找到了meet。但是,对于上面给出的方法,会引出一个问题,即这两个指针在环形链表中是否会一定相遇的问题。下面将对这个问题进行探究

slowfast这两个指针步数差为任意值时,是否一定会在环形链表中相遇(重点):

按照文章上面所规定的:slow每次向后访问一个结点。fast每次向后访问两个结点,当fast恰好进入环的起始结点时,slow的位置大致如下:

slow恰好位于环的起始位置时,fast的位置大致如下:

此时, fast开始追赶slow。假设,两个指针中间的距离,即红线所标出的距离为n。两个指针每向后按照自己的速度遍历一次,两个指针之间的距离就会缩短1.所以,无论这个距离是奇数还是偶数。都可以找到meet这个结点,即两个指针指向的结点保存的地址相同。

在判断链表是否有环时,对于有环的链表,两个指针会一直按照规定运动下去。对于无环的链表,会存在结点数为奇数和结点数为偶数两种情况。下面给出不带环链表对于这两种情况下,循环是否执行的判定。

(注:上述推论只是局限于一个每次访问一个结点,一个每次访问两个结点的特殊情况。对于更加严格的推论会在本文下方LeetCode.142中详细展开)

对于结点数为奇数的情况下:

 如图所示,当结点数量为奇数且这个奇数>=3时,指针走完这个链表所需要的次数永远是偶数。所以对于结点数量为奇数的链表。指针fast每次向后访问,都有可能恰好访问到链表的最后一个结点。所以需要检测已经被访问的结点中存储地址是否为NULL即可。即fast -> next;

对于结点数为偶数的情况下:

如果像结点数为奇数的情况下去判断结点数量为偶数的链表,可以从图上看到,当结点数=2时,判定为是没有环的。当数量>2且为偶数时,每执行一次fast -> next -> next,后面总是会剩下一个结点。再向后执行一次fast -> next -> next,此时的fastNULL,所以,偶数结点的情况下,指针是有可能访问到NULL的,因此只需要判定fast是否为空即可。

将上面的过程用代码表示,即:
 

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

执行结果如下:

LeetCode.142——环形链表Ⅱ: 

(重点)探讨访问结点数不同的两个指针是否一定可以在环形链表中相遇:

 

如上图所示的一个环形链表,创建slow,fast两个指针,其中slow每次向后面访问一个结点。fast每次向后面访问三个结点。所以,两个指针每按照自己的速度向后进行一次访问,就会拉开两个结点的距离。当slow恰好走好环的起点,即:start时,两个指针所在的位置差不多由下面的图来表示:
因为 fast的访问速度大于slow,所以,可以上图理解成一个追赶问题。将两个指针之间用红线画出的距离用M表示。所以,两个指针同时运动一次,距离就会变成M-2

假设M是一个偶数,则,两个指针同时追赶n次后,二者之间的距离会变成M-2*n,并且一定会出现距离为0的情况,即两个指针同时在meet点。

假设M为奇数,则,两个指针同时追赶n次后,二者之间的距离会变成M-2*n,但并不会出现二者之间距离为0的情况。而是会出现M = 1,M = -1的情况。即一个是fast慢于slow一个结点的距离,一个是fast快于slow一个结点的距离。对于M = -1的情况,可以由下面的图像表示:

假设,整个环的长度为C,则图中用蓝色线所标出的两个指针之间的距离就变成C-1

假设, C-1为偶数,则会重复M为偶数的情况。两个指针会相遇。

假设,C-1为奇数,则会重复M为奇数的情况。两个指针不会相遇,而是无限循环两个指针之间的距离为1的情况。

假设slow每次向后访问一个结点,fast向后访问4个结点。两个指针同时向后访问,两个指针之间的距离差是3个结点的长度。对于两个指针会不会相遇的问题,就需要分成M%3 == 0,M%3 == 1,M%3 == 2三个情况来谈论。方法与上面的类型,不再展开叙述。

前面说到,当slow每次向后访问一个结点,fast每次访问两个结点时,二者一定可以在环内相遇。又因为在相同的访问次数下fast所访问的结点,是slow的两倍。如果用距离来表示访问的结点的数量,上面的话也可以理解为,在相同的时间里,fast所走过的距离是slow走过距离的两倍。当二者恰好在meet相遇时,即:

L来表示头结点到环入口结点的距离,用X表示环入口结点到meet的距离。

此时,指针slow所走过的距离可以表示为L+X,指针fast所走过的距离可以表示为L+N+X。又因为上面说到,fast走过的距离是slow走过距离的两倍,所以2(L+X) = L+C+X,化简后可得:L = C - X。但是,如果当环的长度C远小于L时,本式不成立。此时:

若环的长度C远小于L时,当slow位于L/2时,fast恰好位于环的起点。即:

当 slow恰好走到环的起点时,此时因为L >>C,所以,fast已经走过nC,因此。当二者在meet相遇时,二至所走过的距离可以表示为:

                                         2(L+X) = L+n*C+X

化简后可得:
                                         L = n*C-X

为了便于理解上述公式的含义,可以进一步化简为:

                                        L = (n-1)C +C-X

其中,根据上面给出的图像,可以得出上述公式所表达的含义,即:当一个指针一次访问一个结点,另一个指针一次访问两个结点的前提下,一个结点从头结点走到环入口点start的距离L,恰好等于另一个指针从meet点走向start点的距离,即:一个指针从头指针出发,一个指针从meet点出发,二至一定会在start点相遇。在解决这个题目时,将会用这个结论:

题目如下:

思路一:

 题目要求返回入环的第一个结点,即返回上面图中的start点。通过上面给出的结论。即:一个指针从头指针出发,一个指针从meet点出发,二至一定会在start点相遇。可以得到解决题目的方法:

1. 找到两个指针在环中相遇的meet点。

2.让头指针head和在meet点的slow同时向后遍历,二者相遇的那个点就是入环的第一个结点。

代码表示为:

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* slow = head, *fast = head;
    while( fast && fast -> next)
    {
        fast = fast -> next -> next;
        slow = slow -> next;
        if( slow == fast)//存在相遇点,两个指针相遇
        {
            struct ListNode* meet = slow;
            while( head != meet)
            {
                meet = meet -> next;
                head = head -> next;
            }
            return meet;
        }
    }
    return NULL;  
}

执行结果如下:

 思路二:

如果当两个指针在meet点相遇后,取meet->next,用newnode来表示这个点。即meet后的一个点。并且让结点meet与后面的点断开链接,即:meet -> next = NULL,此时的链表可以用下图表示:

再对上面的图片进行更改,即:

 

通过对图像的改进,可以题目改进为上篇文章LeetCode——单链表相关题目(持续更新)_起床写代码啦!的博客-CSDN博客 中的寻找相交结点问题。这里只给出代码,不过多解释:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* curA = headA,*curB = headB;
    int lenA = 0,lenB = 0;
    //计算headA为开头的链表的长度及最后的结点地址
    while( curA -> next)
    {
        curA= curA -> next;
        lenA++;
    }
    //计算headB为开头的链表的长度及最后的结点地址
    while( curB -> next)
    {
        curB = curB -> next;
        lenB++;
    }

    //检查两个链表最后一个结点的地址是否相等。相等则说明有交点
    if( curA != curB)
    {
        return NULL;
    }
    
    //计算lenA和lenB的绝对值差值
    int gap = abs( lenA - lenB);

    //检查lenA和lenB哪个更长
    struct ListNode* longlist,*shortlist;
    if( lenA > lenB)
    {
        longlist = headA;
        shortlist = headB;
    }
    else
    {
        longlist = headB;
        shortlist = headA;
    }

    //longlist先走gap步
    while( gap--)
    {
        longlist = longlist -> next;
    }

    //上下链表的起点位置没有结点数差,再一起遍历,寻找相交点
    while( longlist != shortlist)
    {
        longlist = longlist -> next;
        shortlist = shortlist -> next;
    }
    return longlist;
    
}

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* slow = head, *fast = head;
    while( fast && fast -> next)
    {
        fast = fast -> next -> next;
        slow = slow -> next;
        if( slow == fast)//存在相遇点,两个指针相遇
        {
            struct ListNode* meet = slow;
            struct ListNode* newhead = meet->next;
            meet->next = NULL;
            return getIntersectionNode(newhead,head); 
        }
    }
    return NULL;
    
}


 

 

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

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

相关文章

TCP特点UDP编程

目录 1、tcp协议和udp协议 2、多线程并发和多进程并发: (1)多进程并发服务端 (2)多进程并发客户端: 3、tcp: 4、粘包 5、UDP协议编程流程 (1)服务器端: (2)客户端: 6、tcp状…

成集云 | 乐享问题邀请同步企微提醒 | 解决方案

源系统成集云目标系统 方案介绍 腾讯乐享是腾讯公司开发的一款企业社区化知识管理平台,它提供了包括知识库、问答、课堂、考试、活动、投票和论坛等核心应用。这个平台凝聚了腾讯10年的管理经验,可以满足政府、企业和学校在知识管理、学习培训、文化建…

Java实现钉钉企业内部应用机器和自定义机器人发送消息

前言 公司让写一个服务监控的功能,当监测到服务停止时,向钉钉群里推送报警信息。之前大概看到钉钉的开放平台的API文档,好像能群发消息的只有机器人。 钉钉开放平台目前提供三种机器人: 企业内部应用机器人 群模板机器人 自定义机器人 本来向用自己比较熟悉的自定义机器人…

8年经验之谈 —— 基于jmeter的性能全流程测试

01、做性能测试的步骤 1、服务器性能监控 首先要在对应服务器上面安装性能监控工具,比如linux系统下的服务器,可以选择nmon或者其他的监控工具,然后在jmeter模拟场景跑脚本的时候,同时启动监控工具,这样就可以获得jm…

Android Studio run app 设置 release 模式

背景 为验证我们的 SDK 集成在客户应用上的质量,需要我们的测试环境尽量的与客户应用保持一致。客户普遍都会打 release 包并混淆,然后进行上线应用,因此我们在测试过程中也需要使用 release 包进行验证。对于 Android Studio 运行项目&…

Jmeter数据驱动 —— csv高效用例

目录 1、设置测试用例,创建用例数据文件:testcase.csv 2、新建一个线程组,命名为:数据驱动,添加配置元件-HTTP请求默认值,配置好IP地址和端口号 3、添加逻辑控制器-循环控制器。循环控制器的作用可以控制…

【C++】运算符重载 | 赋值运算符重载

Ⅰ. 运算符重载 引入 ❓什么叫运算符重载? 就是:运用函数,将现有的运算符重新定义,使其能满足各种自定义类型的运算。 回想一下,我们以前运算的对象是不是都是int、char这种内置类型? 那我们自定义的“…

CST HFSS MATLAB参数方程定义曲面绘制

CST HFSS 函数定义曲面绘制 简介环境HFSSCSTMATLAB 简介 若在柱坐标系中半径r随z和phi都会变,无法使用一般的方法绘制,这时可以使用参数方程定义的曲面来绘制。举一个例子如下, r 100 0.5 ( c o s ( 0.2 ∗ p i ∗ z ) − 1 ) c o s ( φ …

Ganache 本地测试网远程连接

文章目录 前言1. 安装Ganache2. 安装cpolar3. 创建公网地址4. 公网访问连接5. 固定公网地址 前言 Ganache 是DApp的测试网络,提供图形化界面,log日志等;智能合约部署时需要连接测试网络。 Ganache 是一个运行在本地测试的网络,通过结合cpol…

哪些人适合参加大数据培训班?

互联网加速职场变革,大数据浪潮席卷全球。日前,Python、大数据、人工智能是当今最热门的话题。大数据存储、大数据分析、 人工智能等开发人才需求旺盛。 大数据培训班有大数据分析培训班、大数据开发培训班,JAVA培训班 大数据班适学人群…

【RabbitMQ】RabbitMQ整合SpringBoot案例

文章目录 1、前情提要【RabbitMQ】2、RabbitMQ-SpringBoot案例 -fanout模式2.1 实现架构总览2.2 具体实现2.2.1生产者2.2.1消费者 1、前情提要【RabbitMQ】 【RabbitMQ】消息队列-RabbitMQ篇章 RabbitMQ实现流程 2、RabbitMQ-SpringBoot案例 -fanout模式 2.1 实现架构总览…

快速学习GO语言总结

备注:本博客将自己初步学习GO的总结进行分享,希望大家通过本博客可以在短时间内快速掌握GO的基本程序编码能力,如有错误请留言指正,谢谢! 一、初步了解Go语言 (一)Go语言诞生的主要问题和目标…

Elasticsearch:语义搜索 - Semantic Search in python

当 OpenAI 于 2022 年 11 月发布 ChatGPT 时,引发了人们对人工智能和机器学习的新一波兴趣。 尽管必要的技术创新已经出现了近十年,而且基本原理的历史甚至更早,但这种巨大的转变引发了各种发展的“寒武纪大爆炸”,特别是在大型语…

【数据结构】_7.二叉树

目录 1.树形结构 1.1 树的概念 1.2 树的相关概念 1.3 树的表示 1.4 树在实际中的应用—表示文件系统的目录树结构 ​编辑​2.二叉树 2.1 概念 2.2 特殊二叉树 2.3 二叉树的性质 2.4 二叉树的存储结构 2.4.1 顺序存储结构(数组存储结构) 2.4.2…

7个改变玩法规则的ChatGPT应用场景

ChatGPT因各种原因受到了广泛关注:ChatGPT可以充当各种改善生活改进工作的小助手,如内容写手、客户支持、语言翻译、编码专家等等。只需在你的聊天内容中添加适当的提示,人工智能将为你提供各项支持。[1] 1.ChatGPT作为内容写手 通过AI的帮助…

flink jira 提交开源bug

注册apache issue账号,并申请flink空间的权限后. 提问题/bug 查看已经提交的问题:

PHP“牵手”淘宝商品评论数据采集方法,淘宝API接口申请指南

淘宝天猫商品评论数据接口 API 是开放平台提供的一种 API 接口,它可以帮助开发者获取商品的详细信息,包括商品的标题、描述、图片等信息。在电商平台的开发中,详情接口API是非常常用的 API,因此本文将详细介绍详情接口 API 的使用…

SRM系统询价竞价管理:优化采购流程的全面解析

SRM系统的询价竞价管理模块是现代企业采购管理中的重要工具。通过该模块,企业可以实现供应商的询价、竞价和合同管理等关键环节的自动化和优化。 一、概述 SRM系统是一种用于管理和优化供应商关系的软件系统。它通过集成各个环节,包括供应商信息管理、询…

龙讯旷腾PWmat已部署至曙光智算平台

编者荐语: 近期,龙讯旷腾核心产品PWmat已成功部署至曙光智算AC.sugon.com平台,可为用户提供包括分子建模、第一性原理计算、数据可视化等在内的完备的超级计算云服务,让大家能够轻松上手具有完全自主知识产权的大尺度高性能材料计…

游戏找不到msvcr100.dll解决方法,常见的三种解决方法

在计算机领域,msvcr100.dll是一个非常重要的动态链接库文件。它是Microsoft Visual C 2010 Redistributable的一部分,用于支持Visual Studio 2010的开发环境。然而,在某些情况下,msvcr100.dll可能会出现问题,导致程序无…