搞懂链表环路-快慢指针:原理和代码

快慢指针经常被用在链表相关的一些算法中,具有很高的时间和空间效率。最近在LeetCode上刷题看到了链表环路检测和环路起始节点的访问,看了一些代码和一些大佬的解释,但是感觉说得不是很清楚。所以自己动手推导了一下。

1问题抽象

带有环的链表一定可以表示成下面这种模型。这张图是我从LeetCode一个作者那里抄过来的。

图中a是链表的起点,b是环的起点,c是快慢指针相遇的点,x是a到b经历的节点数,y是b到c经历的节点数,z是c到b的节点数。那么整个链表的长度为 L = x + y + z L=x+y+z L=x+y+z.

2快指针是慢指针的2倍速度

如果将快指针设定为每次前进两步,慢指针每次前进一步,那么考虑三个问题:
1.慢指针到达c时,经历的步数为: S ( 慢 ) = x + y S(慢)=x+y S()=x+y
2.快指针到达c时,经历的步数为: S ( 快 ) = ( L + y ) / 2 = ( x + z y + z ) / 2 S(快)=(L+y)/2=(x+zy+z)/2 S()=(L+y)/2=(x+zy+z)/2
3.快慢指针是同步控制的,显然经历的步数应该相等。也就是 S ( 快 ) = S ( 慢 ) S(快)=S(慢) S()=S(),将相关数值代入有: x + y = ( x + z y + z ) / 2 x+y=(x+zy+z)/2 x+y=(x+zy+z)/2化简居然得到 x = z x=z x=z

也就是说,按照这种方式遍历,当快慢指针相遇时,从链表头到环起点的节点数第一次相遇点到环起点的节点数相同!
这也就意味着,要找出b,只需要用两个指针,分别从a和c开始每次走一步,两个指针相遇时就找到了b.

可以总结,这个找到链表环起点的方法干了两件事:
1.找c,即找等距点;
2.找b,即找环起点。

3 C++代码描述

   ListNode *detectCycle(ListNode *head) {
        if(!head)return NULL;//防止空链表
        
        ListNode* fast=head;
        ListNode* slow=head;
        do
        {
            fast=fast->next;
            if(!fast)return NULL;//防止无环链表,fast前进第一步
            fast=fast->next;
            if(!fast)return NULL;//防止无环链表,fast前进第二步
            
            slow=slow->next;//slow前进一步
        }while(fast!=slow);
        
		//这里已经找到了c,fast和slow都指向了c
        //接下来开始找出b
		fast=head;
        while(fast!=slow)
        {
            fast=fast->next;
            slow=slow->next;
        }
        return fast;
    }

4 快指针是慢指针的三倍速

不妨做一个探讨,让快指针一次前进三步,但慢指针仍然只前进一步。仍然考虑原来的三个问题:
1.慢指针到达c时,经历的步数为: S ( 慢 ) = x + y S(慢)=x+y S()=x+y
2.快指针到达c时,经历的步数为: S ( 快 ) = ( L + y ) / 2 = ( x + z y + z ) / 3 S(快)=(L+y)/2=(x+zy+z)/3 S()=(L+y)/2=(x+zy+z)/3
3.快慢指针是同步控制的,显然经历的步数应该相等。也就是 S ( 快 ) = S ( 慢 ) S(快)=S(慢) S()=S(),将相关数值代入有: x + y = ( x + z y + z ) / 2 x+y=(x+zy+z)/2 x+y=(x+zy+z)/2化简居然得到 2 x + y = z 2x+y=z 2x+y=z

这个时候,问题就复杂了因为不能在顺序访问就找出环的起点了。换一个思路,2倍速之所以成功,是因为消去了方程中的y.

5二倍速算法中c点的存在性

我们上面考虑的三个问题其实有一个暗含的假设,就是一定存在c点,也就是说当快指针一次前进两步,会在一定会在某个时候追上一次只前进一步的慢指针。

然而,这件事情真的一定会发生吗?

至少有一件事情我们可以确信,如果不加控制,链表中存在环,快指针一定会反复地超过慢指针。这个很显而易见。

那么,这个存在性问题可以简单地等同于一个对齐问题,由于快指针只比慢指针多一步,那么当快指针即将接近慢指针时,只可能有下面两种情况:

fast
slow
fast
slow

(表格每一个代表一个节点,不太好画表格,狗头)
(1)如果是第一种情况,那么在经过一次追赶,fast会前进两步,slow只会前进一步,最终相遇;
(2)如果是第二种情况,已经相遇;
其余的情况最终都会到这两步,很明显,如果不清楚试着写一写?

因此,一次前进两步的快指针一定会和一次前进一步的慢指针相遇。当然在我们的大前提下呀。

6重要的事

觉得不错请点赞,收藏或者评论,当然打赏也是可以的。
转载请注明出处。

打击盗版,鼓励创作,靠我们

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值