利用快慢指针判断单链表是否有环(返回first环节点)解法(附在文末)的原创证明及思考

先说结论:

快指针只能一次两步,慢指针只能一次一步。

利用快慢指针判断单链表是否有环(返回first环节点)解法(附在文末)的原创证明及思考_第1张图片

从一个简单问题入手,F和S两人分别以2m/s和1m/s的速度从400m跑道同一点起跑,问在何处第一次相遇。这是一个小学数学的内容,可以转换为追赶问题——F落后S一整圈,结果在起点处追上。这是很好理解的。对于这样简单的模型,无论是从A点或是O点出发,最终都是在A点或O点相遇。然而单链表上的路径是离散不连续的,对于其他不同的速度取值,实际上,只要快慢指针的速度差值不可被环长度约分,就可以保证在“一个整圈的时间”后第一次相遇(条件一)。例如F/S取5/3,那么会在400/2=200处第一次相遇。对于单个人而言,从A出发走整数圈的路程,其终点还是A。

现在来思考另一个问题:

利用快慢指针判断单链表是否有环(返回first环节点)解法(附在文末)的原创证明及思考_第2张图片

图中AO=A'O。如果满足前述条件一,F和S同时从A'处出发,他们会在A处相遇。这是一个等效转换的思维题(见注)。进一步的,如果S从A’处出发,F从A处出发,他们也会在A处相遇。对于单个人而言,从A'出发走整数圈的路程,其终点还是A(结论一)。

有了上述铺垫,我们就可以来聊聊文章题目中提到的解法实现原理。

利用快慢指针判断单链表是否有环(返回first环节点)解法(附在文末)的原创证明及思考_第3张图片

快慢指针可以从环外任一结点处(记为index)同时出发,取A'使得index到A’为整圈的距离(设为N*L)。如果快指针F的速度是慢指针S的整数倍时(充分条件非必要,对于一般情况,其充要条件为N*V(快)%V(慢)=0),当慢指针走到A'时,快指针先走了index到A’的距离,然后从A’走了整数圈的距离,由前述结论一可知此时快指针走到了A处。

好,现在停下来看看我们得到了什么——一个在环上跟首个环结点O距离为A’O的指针,而A'O是index到O整段长度除去整数圈剩下的余数部分,当得到在A处的指针时,我们也就相当于知道了A'O的长度。让两个慢指针分别从index和A点一次一步地走下去,当左边走到A'时,右边转了整数圈又回到了A点,继续分别从A'和A走下去,两指针必然在O点相遇。返回相遇处的结点O,即为首个环上结点。

在实际代码实现中,为不遗漏每一个结点,快指针只能一次走两步,慢指针只能一次走一步。

public class Node {
    int value;
    Node next;

    public Node getRing(Node index) {
        Node F = index.next.next;
        Node S = index.next;
        while (F != S) {
            if (F.next == null || F.next.next == null)
                return F;
            F = F.next.next;
            S = S.next;
        }
        S = index;
        while (F != S) {
            F = F.next;
            S = S.next;
        }
        return F;
    }
}

注:等效的实质是无论F还是S经过O点后的每一刻所在位置与出发点无关(直道出发还是圈上出发,经过O点后位置相同)。进一步的,对于普遍的情况(包括直道长度大于整圈的情形),在慢指针S(或F)经过O点后的任一时刻,S和F(或F)从不同出发点出发到达的位置时时刻刻相等。理解这一点的话,就不难理解它们会在圈上的等效出发点相遇了。你可能会好奇,对于直道长度大于整圈的情形是否能够像最初的例子一般寻找等效出发点,是可以的——只不过等效于F和S从整圈上出发等S跑完N圈的时候开始计算第一次相遇(N等于直道长度除以整圈长度取整)。

文章内容皆原创,转载请注明作者。

你可能感兴趣的:(算法与数据结构,开发语言,链表,c++,java)