申明:
这个题目并不是编程之美收录的题目,但是笔者在面试过程中,遇到过至少两次,其中一次就是创新工场。因此,笔者感觉有必要在这里和大家分享一下这个题目。
问题描述:
一个链表(只有next和data两个域),判断其是否存在环。(环入口可能存在任何位置,并不是只有头结点)。如果存在环,并求解环的入口位置。
问题一:
对于问题一,在面试创新工场的时候,由于笔者自己实力有限,并没有给出完全正确的代码。其实问题一的求解可以用生活中的例子来解决——两个人在操场上跑步。将链表看成跑道,如果该跑道就是一条直线,那么两个人跑步,跑得快的人永远不可能在超过慢者后再次和慢者相遇,如果跑道是环状的,那么快着必然在超越慢者后再次和慢者相遇。
基于这种思想,我们设置两个指针p1和p2,初始情况下,同时指向链表的头结点,p1以一个节点单位进行移动,p2以两个节点为单位进行移动。如果链表没有环,那么任意一个节点最后都将指向NULL,而如果存在环,在环中,一定存在p1和p2同时指向同一节点。
这时,有同学可能要问,对于跑步,一定相遇是很好理解的,但是对于p1和p2的移动,为什么当有环存在时,一定会相遇呢,难道不会刚好错开吗?其次,为什么p2节点要以两个节点单位进行移动,而不是三个,甚至更多?
p1以一个节点单位移动,p2以两个节点单位移动,那么他们一定相遇!试想一下,如果链表存在环,在某一时刻,p1和p2都进入了环(p2肯定比p1先进入),由于p2相对于p1的移动速度多一个节点单位,因此将可以将p1看出没有移动,p2以一个单位移动,显然,p2最后必然和p1相遇,而且p1最多遍历环一圈。如果p2相对于p1的移动速度快两个节点单位,那么有可能出现p2跳过p1的情况,同理,p2以更快的速度移动的情况。
具体伪代码如下:
bool circleexist(Node* head)
{
If(head== NULL)
returnfalse;
Node*p1=head;
Node*p2=head;
While(p1)
{
p1=p1->next;
p2=p2->next->next;
if(p1== p2)
returntrue;
}
returnfalse;
}
问题二:
判断环的入口,这个问题说白了就是个数学问题。知道这样的一种结论,即“在环中相遇点到入口的距离等于头结点到入口的距离”就能很快的解决问题二,关于这个结论证明过程如下:
当链表存在环时:
设p1和p2在环中相遇,相遇点距离入口点的距离为x,头结点到环入口点的距离为y,那么p1节点所有的距离d1=x+y。
从问题一中发现,p2节点在和p1相遇时,比p1节点多移动了环一圈的距离r,又p2节点的移动速度是p1的两倍,因此,p2节点移动的距离d2=2*d1=2x+2y=x+y+r。
即从2x+2y=x+y+r得到x+y=r。
X=r-y。
证得。
有了这个结论后就可以让两个指针从头结点和相遇点同时出发,相遇的地方即环的入口。