一文解答你关于“轨道问题”的所有疑问!(有环链表问题)

问题描述:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL。

我看过很多博客,对于最优解法的解释无非两个字,神奇,并没有说明如何构思出这样的思路。这篇文章是我看过《Elements of programming》相关分析后,结合题目总结出的解题思路,教给你如何构思快慢指针的思路,认真看完一定会有收获的。

相信很多同学和我一样,第一次看到这个题目完全没有思路,能想到的只有最暴力的办法,用一个指针从头开始遍历,遍历时,记录每一个点的地址,然后新地址与所有旧地址比较,确定有无环,如果这个指针最终变为null,则无环。但是自己也能想到这样的方法必然不可行,时间复杂度和空间复杂度都过高。该怎么优化呢?

灿烂至极归于平淡,我们从一个最简单的生活实例入手。想象在马路的起点,安排两辆汽车,第一辆速度快,记为fast,第二辆速度慢,记为slow,它们同时出发。如果公路有环,那么fast和slow必然会在环上某点相遇。如果公路无环有终点,那么fast会到达终点,不会与slow相遇。

这就可以解决第一个问题,链表是否有环。在链表头安排两个指针,fast和slow,看他们是否相遇即可,这也是很多博客的思路。

但是且慢,这里有个很关键的问题,就是从连续到离散的问题。公路上行驶的两辆车,轨迹是连续的,必然会相遇。但是链表上的两个指针,轨迹是离散的。两条离散的轨迹有交点,但是交点并不在公共点上,怎么办?例如下图,两条轨迹的确相交了,但是轨迹的交点处并没有公共点。

所以我们需要证明,快慢指针在环上一定会有公共交点。在证明之前,先明确几个符号,便于讨论。

如图,链表长度记为o,环的长度记为c,链表上除环以外的部分称为柄(只是个称呼),其长度记为h。那么显然o = h + c。

现在先把注意力放到环上的两点,设环上两点分别为A和B,其位置也用A和B表示,设A > B,那么dist(A->B) = B - A + c,dist(B->A)=A - B。显然dist(A->B) + dist(B->A) = c。当distA->B = 0或 distB->A = 0时,A和B就相遇了。

有了上面的引理,我们再来观察slow和fast的轨迹。当slow达到入口点时,fast已经在环中等候多时,此时设dist(fast-slow) = d。所谓让fast和slow逐步相遇,就是要让d逐步减小到0或是增加到c。那一个整数该怎么逐步减小到0呢?很简单,让他每次减小1!这就有了fast每次走2步,slow每次走1步。slow每次走1步,fast每次走2步的话,dist(slow->fast)就每次增加1,从而dist(fast->slow)就每次减小1,最终一定会减小到0,也就是一定会相遇!

上面一段,从相遇推出了每次fast走两步,slow走1步,当然也可以反向推导,很容易。我之所以这样推导,是想告诉读者,任何一个设定都不是凭空的,都是有原因的。(包括《Elements of programming》在内,也不是这样分析的)

好了,到此为止,第一个问题已经圆满解决,即如何确定链表是否有环。那就是使用两个指针,一个fast,一个slow,它们都从头结点出发,在每次迭代中,fast走两步,slow走1步。如果它们相遇,那么说明链表有环。如果不相遇,即fast->next == nullptr,fast走到了终点,那么链表无环。

下面来解决第二个问题,若链表有环,如何确定链表的入口结点?

要解决第二个问题,就要充分利用第一个问题中我们得到的信息。其实第二个问题,也可以换一种方式表述,即确定链表的结构,即求解h和c分别是多大。设fast和slow相遇时,经过了n次迭代,即fast走了2n步,而slow走了n步。此时fast的位置是

(2n - h)$\%$c + h

同理,此时slow的位置是

(n - h) + h

为什么slow不需要对c取余数呢?因为slow在环上必然没有走够一圈。d < c,slow从进入环开始每走 1 步,d减小1到0停止,故而slow必然在环上没走够1圈,所以slow - h必然是小于c的,也就不用对c取余了。从而有

(2n - h) $\%$ c + h = (n -h) + h

对这个式子化简,首先可得(假设商是r)

2n - h = rc + n - h

所以有

n = rc

这个式子可就不得了了!这个式子是什么意思呢,n是c的整数倍!再回想一下我们的目标,我们的目标是把h求解出来,所以要强行把h加到上面的式子里,对其变形,得

(n + h - h) $\%$ c + h = h

这个式子是什么意思呢?一个结点从头结点开始往后走h步,另一个结点从n开始先走h步,这两个结点一点会相遇!又因为从头走了h步,那么这个相遇点就必然是入口结点,也就是我们要找的结点!(这里是数学中的同一法)

OK,问题已经解决了,还有什么疑问欢迎评论区留言。

对了,如果有收获希望能点赞收藏分享三连,谢谢。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值