不同于其他文章对C语言的介绍,本期将为大家带来两道快慢指针的经典题目。
先来看看两道题目吧!
显然第二道是第一道的进阶版,我们便从第一道开始说起。
第一题
这道题要求我们知道是否有环。那么只有两种情况,链表无环,最后会指向NULL;链表有环,最后一直循环。
但是我们不能通过计数的方式来得到,因为链表可能非常长,我们在得到NULL之前,是永远不会知道链表的长度的。
所以这个时候,快慢指针就有了用场。
快指针走两步,慢指针走一步,达到了存在“时间差”的效果。
假设无环存在,那么大部分情况下快指针会先到NULL(如果元素仅有一个,那么两者都会到NULL,这也是while里的判断是fast && fast->next的原因之一)
为什么要用fast && fast->next来判断?原因是链表不一定是偶数个,这样会导致fast->next直接是NULL了,就不存在fast->next->next了。
所以如果跳出while,将直接得到无环的结论,return false。
假设有环存在,快慢指针真正派上了用场。
小学的时候,我们都学过追击问题吧?
这里的快慢指针也可以被视作追击问题的衍生。
有环存在,就意味着快慢指针会同时进入环内。进入环内之后,便是追击问题了。
在环内计算时,直接算相对速度就好。
如果将慢指针的速度视为1的话,那么快指针的速度就是2,相对速度也就是2-1=0。
此刻假设慢指针进入环内的时候,与快指针的位置相差C个单位。
设环的长度为N,易知追上的“时间”是(N-C),也就得到了这种方式可行的结论。
问题延申
如果快慢指针的速度差不为1,为2,为3,能起到一样的效果吗?
即fast=3,slow=1或者fast=4,slow=1这样。
我们开始讨论这个问题。
仍然从追击问题的视角来看这个问题。
fast和slow的相对距离不一定是C,也可能是C+x*N(其中x为未知圈数,N为圈的总长度)
换句话说,fast可能在slow走第一圈时相遇,也有可能在slow走第n圈时相遇......
那么,只需要(C+x*N)能被(n-1)整除,那么就可以相遇。
这意味着,快慢指针的速度差可能会影响是否能追上。
但是当n取2的时候,就一定能追上,因为1整除任何数。
第二题
第二题在第一题的基础上,让我们返回环的起始位置。
我们肯定是选择最简单的情况,即快指针速度是慢指针2倍。
先上代码:
这里用到了一个结论,同时从快慢指针的位置和头结点开始以1的速度走,相遇点就是头节点。
下面证明这个结论:
我们从之前的研究中可以知道,如果快指针是慢指针的两倍,那么一定会在slow的第一圈相遇。
S(slow)=L(环之前的长度)+N(slow已走过的路)
S(fast)=L+x*C(x为圈数,C为整个环的长度)+N(slow已走过的路)
因为S(fast)=2*S(slow)
所以x*C=L+N;
所以L=x*C-N;
那么head走了L的时候,meet位置走了x*C-N,从图上就可以看出,meet一定在环的头节点处!
也就是说,当head和meet相遇时即是头结点。
这就是我为大家带来的两道经典题目,如有错误请多指正!
祝各位学习顺利~