检测链表是否存在环路
有一种简单的做法叫FastRunner/SlowRunner法。FastRunner一次移动两步,而SlowRunner一次移动一步。这就好比两辆赛车绕着同一条赛道以不同的速度前进,最终必然会碰到一起。
聪明的读者可能会问:FastRunner会不会刚好“越过”SlowRunner,而不发生碰撞呢?绝无可能。假设FastRunner真的越过了SlowRunner,且SlowRunner处于位置i,FastRunner位于位置i+1。那么,在前一步,SlowRunner就处于位置i-1,FastRunner处于位置((i+1)-2)或i-1。也就是说,两者碰在一起了。
什么时候碰在一起?
假定这个链表有一部分不存在环路,长度为k。
若运用上述算法,FastRunner和SlowRunner什么时候会碰在一起呢?
我们知道,SlowRunner每走p步,FastRunner就会走2p步。因此,当SlowRunner走了k步进入环路部分时,FastRunner已走了总共2k步,进入环路部分已有2k-k步或k步。由于k可能比环路长度大得多,实际上我们应该把它写作mod(k, LOOP_SIZE)步,并用K表示。
对于之后的每一步,FastRunner和SlowRunner之间不是走远一步就是更近一步,具体要看观察的角度。也就是说,因为两者处于圆圈中,当A以远离B的方向走出q步时,同时也是向B更近了q步。综上,我们得出以下几点:
(1)SlowRunner处于环路中的0步位置;
(2)FastRunner处于环路中的K步位置;
(3)SlowRunner落后于FastRunner,相距K步;
(4)FastRunner落后于SlowRunner,相距LOOP_SIZE-K步;
(5)每过一个单位时间,FastRunner就会更接近于SlowRunner一步。
那么,两个节点什么时候相遇?若FastRunner落后于SlowRunner,相距LOOP_SIZE-K步,并且每经过一个单位时间,FastRunner就走近SlowRunner一步,那么,两者将在LOOP_SIZE-K步之后相遇。此时,两者与环路起始处相距K步,我们将这个位置称为CollisionSpot。
如何找到环路起始处?
现在我们知道CollisionSpot与环路起始处相距K个节点。由于K=mod(k, LOOP_SIZE)(或者换句话说,k=K+M*LOOP_SIZE,其中M为任意整数),也可以说,CollisionSpot与环路起始处相距k个节点。例如,若有个环路长度为5个节点,有个节点N处于距离环路起始处2个节点的地方,我们也可以换个说法:这个节点处于距离环路起始处7个、12个甚至397个节点。
至此,CollisionSpot和LinkedListHead与环路起始处均相距k个节点。
现在,若用一个指针指向CollisionSpot,用另一个指针指向LinkedListHead,两者与LoopStart均相距k个节点。以同样的速度移动,这两个指针会再次碰在一起——这次是在k步之后,此时两个指针都指向LoopStart,然后只需返回该结点即可。
将全部整合在一起
总结一下,FastPointer的移动速度是SlowPointer的两倍。当SlowPointer走了k个节点进入环路时,FastRunner已进入链表环路k个节点。也就是说FastRunner和SlowRunner相距LOOP_SIZE-k个节点。
接下来,若SlowPointer每走一个节点,FastRunner就走两个节点,每走一次,两者的距离就会更近一个节点。因此,在走了LOOP_SIZE-k次后,它们就会碰在一起。这时两者距离环路起始处就有k个节点。
链表首部与环路起始处也相距k个节点。因此,若其中一个指针保持不变,另一个指针指向链表首部,则两个指针就会在环路起始处相会。
根据之前的4部分描述,就能直接导出下面的算法。
(1)创建两个指针:FastPointer和SlowPointer。
(2)SlowPointer每走一步,FastPointer就走两步。
(3)两者碰在一起时,将SlowPointer指向LinkedListHead,FastPointer保持不变。
(4)以相同速度移动SlowPointer和FastPointer,一次一步,然后返回新的碰撞处。
下面给出算法代码:
LinkedListNode findBegining(LinkedListNode head) {
LinkedListNode slow = head;
LinkedListNode fast = head;
/*找出碰撞处,将处于链表中LOOP_SIZE-k步的位置*/
while(fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if(slow == fast) { // 碰撞
break;
}
}
/*错误检查,没有碰撞处,也即没有回路*/
if(fast == null || fast.next == null) {
return null;
}
/*将slow指向首部,fast指向碰撞处,两者距离环路
*起始处k步,若两者以相同速度移动,则必定会在环
*路起始处碰在一起*/
slow = head;
while(slow != fast) {
slow = slow.next;
fast = fast.next;
}
/*至此两者均指向环路起始处*/
return fast;
}