判断一条链表上是否存在环
方法一、简单粗暴的方法,两套循环遍历链表,查看是否存在相同的节点,如果有,则存在环;没有,则不存在环,时间复杂度O(n^2),面试官肯定不愿意看到这种解决方式。
是否存在时间复杂度为O(n)的算法求解该问题呢?有的有效的方法(内存开销更少的方法)是由Floyd提出的,所以该方法称为Floyd环判定算法。
方法二、该方法使用两个在链表中具有不同移动速度的指针。一旦它们进人环就会相遇,即表示存在环。这个判定方法的正确性在于,快速移动指针和慢速移动指针将会指向同一位置的唯一可能情况,就是整个或者部分链表是一个环。 设想一下,乌龟和兔子在一个轨道上赛跑。如果它们在一个环上赛跑,那么跑得快的兔子将赶上乌龟。下面的图例展示了Floyd算法的过程。
从下图中可以发现,执行最后一步后, 它们将在环中可能并非起点的某一点相遇。
注意: 龟指针slowPtr每次后移1个结点。兔指针fastPtr每次后移2个结点
/**
* 链表是否存在环
* Floyd环判定算法
*
* @author zhongling
* @since 2018年07月26日
*/
public class LinkedListContainsLoop {
class ListNode{
ListNode next;
}
boolean isLinkedListContainsLoop(ListNode head){
if(head==null){
return false;
}
ListNode slowPtr=head;
ListNode fastPtr=head;
while(slowPtr.next!=null && fastPtr.next.next!=null){
slowPtr=slowPtr.next;
fastPtr=fastPtr.next.next;
if(slowPtr==fastPtr){
return true;
}
}
return false;
}
}
衍生问题1------找出环的入口点(起点)
当fast按照每次2步,slow每次一步的方式走,发现fastPtr和slowPtr重合,确定了单向链表有环路。接下来,让slowPrt回到链表的头部,然后slowPtr和fastPtr各自从自己的位置(fastPtr从两个指针相遇的位置position出发)沿着链表出发,每次步长1,那么当fastPtr和slowPtr再次相遇的时候,就是环路的入口了。
下面给出论证过程:(参考博主https://blog.csdn.net/u012534831/article/details/74231581)
假设一个链表是下面这样的:
ListNode findLinkedListLoopBegin(ListNode head){
if(head==null){
return null;
}
ListNode slowPtr=head;
ListNode fastPtr=head;
boolean isLinkedListContainsLoop=false;
while(slowPtr.next!=null && fastPtr.next.next!=null){
slowPtr=slowPtr.next;
fastPtr=fastPtr.next.next;
if(slowPtr==fastPtr){
isLinkedListContainsLoop=true;
break;
}
}
if(isLinkedListContainsLoop){
slowPtr=head;
while(slowPtr!=fastPtr){
slowPtr=slowPtr.next;
fastPtr=fastPtr.next;
}
return slowPtr;
}
return null;
}
设环长为n,非环形部分长度为m,当第一次相遇时显然slow指针行走了 m+A*n+k(A表示slow行走了A圈。附:A*n 是因为如果环够大,则他们的相遇需要经过好几环才相遇)。fast行走了 m+B*n+k。
上面我们说了slow每次行走一步,fast每次行走两步,则在同一时间,fast行走的路程是slow的两倍。假设slow行走的路程为S,则fast行走的路程为2S。
用fast减去slow可得:
S=(B-A)*n
很显然这意味着当slow和fast相遇时他们走过的路程都为圈长的倍数。
接下来,将slow移动到起点位置,如下图:
然后每次两个指针都只移动一步,当slow移动了m,即到达了环的起点位置,此时fast总共移动了 2S+m。 考虑到S为环长的倍数,可以理解为:fast先从链表起点出发,经过了m到达环的起点,然后绕着环移动了几圈,最终又到达环的起点,值为2S+m。所以fast最终必定处在环的起点位置。即两者相遇点即为环的起点位置。
衍生问题2,求环的大小(长度)
当fast按照每次2步,slow每次一步的方式走,发现fastPtr和slowPtr重合,确定了单向链表有环路。接下来,让slowPrt不动,fast 绕着环移动,每次移动一步,计数count加1,当两指针再次相遇时,count即是环的大小