一网打尽带环链表问题(手把着手带你理解,木有基础也能看懂!)

目录

0.前言

1.认识带环链表

2.带环链表OJ问题简述

3.判断是否为带环链表

4. 寻找入环节点(法一:数学推理的魅力)

5. 寻找入环节点(暴力拆解带环链表的魄力)

6.番外:判断是否为带环链表(fast和slow的速度选择)


0.前言

本文所有代码及图片资源都已传入gitee,可自取

3链表OJ题p3(带环问题) · onlookerzy123456qwq/data_structure_practice_primer - 码云 - 开源中国 (gitee.com)https://gitee.com/onlookerzy123456qwq/data_structure_practice_primer/tree/master/3%E9%93%BE%E8%A1%A8OJ%E9%A2%98p3(%E5%B8%A6%E7%8E%AF%E9%97%AE%E9%A2%98)

1.认识带环链表

链表带环问题是一个非常经典的链表OJ题,本文将会进行细致剖析,首先第一个问题:什么是带环的链表呢

我们通常说的单链表是一个节点一个节点线性连接的,最后一个节点指向NULL,代表这个单链表的结束带环链表偏偏不这样,其最后一个节点并不指向NULL,而是又回来指向该单链表中的某个节点

这样就会导致一个非常严重的问题!!!经典的单链表你用cur去遍历这个链表是可以用while(cur){cur = cur->next;}遍历到尾NULL结束的,但是你现在对带环链表进行遍历的话,那cur就永远也走不到NULL了,就相当于cur一直在这个环里一直转圈!!!陷入死循环

 这就是带环链表的特点

2.带环链表OJ问题简述

141. 环形链表 - 力扣(LeetCode)https://leetcode.cn/problems/linked-list-cycle/142. 环形链表 II - 力扣(LeetCode)https://leetcode.cn/problems/linked-list-cycle-ii/

第一道题是给我们一个链表Head,让我们判断这是一个带环链表还是一个不带环链表。

 第二个问题是,如果判断出这是一个带环链表,那么请你找出入环点在哪,如下图,开始入环的节点是2这个节点,我们就是要找出2这个入环节点

下面我们依次解决这两个问题。

  3.判断是否为带环链表

我们如果以cur走到空作为循环的条件,那带环链表就会陷入死循环,这是我们不愿意看到的,那如何才能判断出这是一个带环/不带环链表呢? 

带环链表,那肯定是带环的,现实中我们的操场上的400m环形跑道也是环形的,在环形跑道上总会发生一件事情:套圈!!!尤其是现在环形跑道有一个跑得特别快的人和一个跑得特别慢的人,假设他们在进行5000m比赛,如果他们一直跑跑得特别快的人一定会在某一时刻追上跑得慢的人,然后跑得慢的人的套圈就意味着某个时刻跑得的人追上,即跑得的人相遇。而如果是100m比赛,这种没有圈,即没有环的跑道,就不存在套圈,即跑得特别的人会始终在跑得的人前面直到到达终点,他们永远不会相遇

 现在我们具象化,现在有两个人fast和slowfast2m/s的速度匀速前进,slow1m/s的速度匀速前进,且现在slow在fast前X米,即fast需要追X米才能追上slow,即fast要比slow多跑X米才会相遇。如果fast和slow一直在环中跑,fast总有一个时刻可以追slowX米,和slow相遇

 按照这个思路,如果我们定义两个快慢指针fast和slow,他们同时在起点head出发fast速度一次走两步fast = fast->next->nextslow的速度一次走一步slow = slow->next。

如果链表不带环,就是一条线走到底,那fast/fast->next会首先走到尾空NULL,fast和slow永远不会相遇

而如果链表带环那fast一定会一马当先入环,然后fast开始在环里转圈圈, 等待slow入环,等slow入环时fast和slow环内追击战就开始了!

每fast和slow的走一次,fast和slow就缩小一个节点的距离,若从slow入环的那一刻开始,slow和fast差X节点的距离,slow和fast走X次之后,即fast走2X个节点slow走X个节点,此时fast和slow追上相遇,即fast==slow。这是带环所必然经历的事情。

bool hasCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        //若不带环,fast会一直走到最后一个节点/NULL
        while(fast && fast->next)
        {
            fast = fast->next->next;
            slow = slow->next;
            //入环之后,fast每次缩小与slow一个节点距离,一定会和slow相遇
            if(fast == slow)
            {
                return true;
            }
        }
        //不带环
        return false;
    }

4. 寻找入环节点(法一:数学推理的魅力)

第一个问题的基础上,我们引入第二个问题:如果带环,那入环节点在哪呢?

这其实是个数学问题!!!我们先摆出结论:(第一个问题的基础)如果fast和slow从起点head出发,fast一次走两步,slow一次走一步,如果链表带环,那到最后fast和slow一定会在环中的某个节点相遇这个相遇的节点位置我们记录为meetnode。(第一个问题延伸)然后记录两个节点node1,node2,一个节点从起点head出发,另一个节点从meetnode出发,node1和node2分别都一次走一步node1和node2一定会相遇,且他们相遇的地方就是入环点!!!!!

ListNode *detectCycle(ListNode *head) {
        //判断是否有环
        ListNode* fast = head;
        ListNode* slow = head;
        //无环fast会走完这个链表
        while(fast && fast->next)
        {
            fast = fast->next->next;
            slow = slow->next;
            //如果带环,fast和slow会在环中某个节点相遇
            if(fast==slow)
            {
                //记录相遇位置为meetnode
                ListNode* meetnode = slow;
                //node1从meetnode走,node2从head走
                ListNode* node1 = meetnode;
                ListNode* node2 = head;
                //最终node1和node2一定相遇,且相遇点是入环节点
                while(node1!=node2)
                {
                    node1 = node1->next;
                    node2 = node2->next;
                }
                return node1;
            }
        }
        //无环情况
        return nullptr;
    }

 为什么会这样呢?下面我们证明这个结论:

我们先把fast在环中追到slow的场景塑造出来:

这个带环链表,到达入环点前的要走的直线距离是L个节点环的长度(环的节点数)为C,slow一次走一步,fast一次走两步,同时从起点head出发。

然后一定是fast先入环,slow一次走一步,当slow入环,即slow到达入环点的时候,假设此时fast距离slow有X步即fast要追slowX步才相遇

fast的速度是一次两步,slow的速度是一次一步,每次走完fast与slow缩小一步的距离,那走了X次之后,即fast走了2Xslow走了X,2*X - X = X,fast追上slow,与之相遇。然后我们标记相遇点为meetnode

 根据结论:我们知道如果此时让node1从meetnode出发,node2从head出发,最后node1和node2一定会相遇,且相遇点就是入环点!!!下面我们根据上面的全过程进行推论:

1.slow和fast一直都在走,slow的速度始终是一步fast的速度始终是两步,fast速度始终是slow的两倍,所以直至fast和slow在meetnode相遇,fast走过的总距离一定是slow走过的总距离的两倍!

而我们知道slow走了L距离到达入环点此时fast和slow相距X步,在环中开始fast和slow的追逐战,然后在环中走X次,即fast走2X步,slow走X步后,fast追上slow,即fast和slow在相遇点meetnode相遇。这个全过程slow走了L+X步,fast的速度始终是slow的两倍,所以从这个角度分析fast走的总距离一定是2(L+X)

2.我们知道fast首先走过L,先于slow入环,然后fast在环中逛游,然后等到slow到达入环点的时候此时fast和slow相距X步,一圈的步数是X,所以fast所处的位置是距离入环点C-X处,所以在slow到达入环点之前,fast一定是在环内走了C-X步到预定位置的。

可是fast在环内只能走C-X步吗?

从slow达到入环点之前,在这个过程中fast不仅是走C-X达到预定位置,有可能fast在到达预定位置之后,fast又在环中走了好多圈,也有可能fast一圈也没走完,可以说slow到达入环点之前fast在环中走了多少几圈,走了多少圈都是不一定的。即在slow达到入环点之前,fast在环内走了有可能走了n*C + C-X(n>=0),这样slow到达入环点的时候,fast所处的位置还是距slowX步的。

所以在slow到达入环点的时候fast已经走L + n*C + C-X了,然后slow到达入环点开启追逐战:此时fast和slow相距X,然后fast追了slowX步,即fast走2X步,slow走X步后,两者相遇

我们总结上述过程fast走的总距离是 L + n*C + C-X + 2X = L + n*C + C + X(n>=0)

从上面这个角度我们得出fast和slow相遇之前,fast走得总距离是2(L+X),也是(L + n*C + 2X),2L+2X =  L + n*C + C + X,所以我们得到 L = n*C + C - X。(n>=0)

然后让node1从head走node2从meetnode出发,node2从head走到入环点需要走L步,node1从meetnode出发,在node2走L步到达入环点node1会走C-X + n*C,即node1也会走C-X步到达入环点,然后走n圈还是在入环点,此时我们可以看到node1和node2一定会在入环点相遇

所以结论正确:

(第一个问题的基础)如果fast和slow从起点head出发,fast一次走两步,slow一次走一步,如果链表带环,那到最后fast和slow一定会在环中的某个节点相遇这个相遇的节点位置我们记录为meetnode。(第一个问题延伸)然后记录两个节点node1,node2,一个节点从起点head出发,另一个节点从meetnode出发,node1和node2分别都一次走一步node1和node2一定会相遇,且他们相遇的地方就是入环点!!!!!

5. 寻找入环节点(暴力拆解带环链表的魄力)

第一个问题的基础上fast和slow一定会在环内相遇,记录相遇点为meetnode,然后我们对链表做拆解把meetnode节点与它的下一个节点newhead做分离,即newhead->next = node改为newhead->next = NULL。完成链表的拆解,带环链表变相交链表。

 然后这个环形链表就转化为下面这个问题:

 然后就变成了head和newhead两个相交链表,我们只要求得相交链表的第一个相交节点即为入环点的位置

对于相交链表的思路,可以在我的博客(一网打尽链表的经典OJ题!链表必考笔试题第二弹)中找到,这里我们简述一下思路,先求出分别求出相交链表1和2的长度len1与len2然后算出差距步abs(len1-len2),然后让较长的相交链表先走差距步走完差距步之后两个链表再同时出发直到他们相等就停下,此时停下的相等的节点的位置就是两个相交链表的起始相交节点,即入环点

 ListNode *detectCycle(ListNode *head) {
        //判断是否有环
        ListNode* fast = head;
        ListNode* slow = head;
        //无环fast会走完这个链表
        while(fast && fast->next)
        {
            fast = fast->next->next;
            slow = slow->next;
            //如果带环,fast和slow会在环中某个节点相遇
            if(fast==slow)
            {
                //记录相遇位置为meetnode
                ListNode* meetnode = slow;
                ListNode* newhead = meetnode->next;
                //把meetnode节点的后一个节点newhead的next置空,拆解链表
                meetnode->next = nullptr;
                //然后就变成了head和meetnode两个相交链表
                //我们只要求得相交链表的第一个相交节点即为相遇点
                int len1 = 0,len2 = 0;
                ListNode* cur1 = head;
                ListNode* cur2 = newhead;
                while(cur1)
                {
                    ++len1;
                    cur1 = cur1->next;
                }
                while(cur2)
                {
                    ++len2;
                    cur2 = cur2->next;
                }
                int derta_len = abs(len1-len2);
                ListNode* longlist = head;
                ListNode* shortlist = newhead;
                if(len1<len2)
                {
                    longlist = newhead;
                    shortlist = head;
                }
                while(derta_len--)
                {
                    longlist = longlist->next;
                }
                while(longlist!=shortlist)
                {
                    longlist = longlist->next;
                    shortlist = shortlist->next;
                }
                return longlist;
            }
        }
        //无环情况
        return nullptr;
    }

6.番外:判断是否为带环链表(fast和slow的速度选择)

判断链表是否带环&&如果带环求出入环点位置,这两个问题我们已经得到实质的解决,可是秉承着一颗探索的心,最后我们回到第一个问题。

 如果fast的速度取每次两步,slow的速度取每次一步,我们先假设slow到入环点的时候,fast距离slow是X步,那么在这个场景下fast和slow是一定可以相遇,因为每次fast和slow的速度差是每次1步,也就是说,X可以整除1走X次之后就可以一定可以相遇

那如果fast的速度不是每次两步,而是每次三步,每次四步,甚至每次五步,在环内fast和slow还一定能相遇吗?会不会遇到这种情况:fast可以追上超过slow,却永远不能与slow相遇。当然是有可能的!我们随便举一个例子你就懂了:

 若环的长度C=2,当slow到达入环点的时候,fast和slow差1步,即起始fast和slow的差距为X=1。

 在这种情况下,fast和slow是永远不能相遇的,为什么这么说:fast和slow速度差是3-1=2,即每次两步,即每次走完fast能比slow多走2步。运用高中物理运动学的知识,我们可以采用参考系相对静止的方法,让slow静止fast以每次2步的在走,此时fast和slow相遇,与fast走每次3步,slow每次1步是等效的。这是第一个我们要使用的技巧。同时我们也可以看到fast能否和slow相遇,看的并不是fast和slow的绝对速度,而是fast和slow的相对速度差!

我们现在在参考系相对静止的场景下,让slow静止,fast以相对速度差前进:能否相遇看的就是能不能让fast走到slow的位置,那fast如何才能走到slow的位置呢,最简单的当然是fast正好走X步到达slow所在的位置,但是fast的相对速度差,即每次2步,不一定可以整除X,如果X是奇数,即如图X==1,即很有可能fast无法正好一步到位slow,而跑到slow的前一个位置。如上面一个例子,slow静止,fast每次两步,fast每次走完都会回到原位,永远不可能与slow相遇!!!而如果fast的速度取每次两步,slow的速度取每次一步,即相对速度差是1时,那相遇就是板上钉钉的事情了。那为什么相对速度差是非1(如上例是2)的时候,有可能遇到fast和slow永远无法相遇的情况呢?

我们仍然以相对速度差为2举例,做出如下具体分析:

 如果相对速度差为3,从这个例子我们推而广之,那fast和slow能否相遇的命题就是这样的:

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值