问题描述:在单向链表中,每个结点都包含一个指向下一个结点的指针,最后一个结点的这个指针被设置为空。但如果把最后一个结点的指针指向链表中存在的某个结点,就会形成一个环,在顺序遍历链表的时候,程序就会陷入死循环。

问题:如何检测一个链表中是否有环,如果检测到环,如何确定环的入口点(即求出环长,环前面的链长)。

[笔试面试]单链表如何检测有环,环入口,环长,环前长度——快慢指针法(百度JAVA面试)_检测有环

一种比较耗空间的做法是,从头开始遍历链表,把每次访问到的结点(或其地址)存入一个集合(hashset)或字典(dictionary),如果发现某个结点已经被访问过了,就表示这个链表存在环,并且这个结点就是环的入口点。这需要O(N)空间和O(N)时间,其中N是链表中结点的数目。

 

如果要求只是用O(1)空间、O(N)时间,应该怎么处理呢?

 

其实很简单,想象一下在跑道上跑步:两个速度不同的人在操场跑道上一圈一圈地跑,他们总会有相遇的时候。因此我们只需要准备两个指针,同时从链表头出发,一个每次往前走一步,另一个每次往前走两步。如果链表没有环,那么经过一段时间,第二个(速度较快的)指针就会到达终点;但如果链表中有环,两个指针就会在环里转圈,并会在某个时刻相遇。

第一个(速度慢的)指针在环里转满一圈之前,两个指针必然相遇。

定义两个指针fastslow,二者的初始值都指向头,slow每次前进一步,fast每次前进两步,两个指针同时向前移动,快指针每移动一次都要跟慢指针比较,直到当快指针等于慢指针为止,就证明这个链表是带环的单向链表,否则,证明这个链表是不带环的循环链表(fast先行到达尾部为NULL,则为无环链表)。

[笔试面试]单链表如何检测有环,环入口,环长,环前长度——快慢指针法(百度JAVA面试)_百度面试dictionary_02

在知道链表内有环后,求环长是一件非常简单的事情,只要从刚才那个相遇点开始,固定P2,继续移动P1,直到P1P2再次相遇,所经过的步数就是环长。(因为此进两个指针都在环内)

怎么求环前面那段子链的长度呢?很简单,让P1P2都回到链表起点,然后让P2先往前走L(表示一圈)次(每次往前走一步),然后P1P2再同时往前走,当它们再次相遇时,P1所走的步数就是环前面的子链长度。


代码实现:

def CheckRing(head): 
  l1= 0  # length of the chain before thering 
  l2= 0  # length of the ring 
 
  #Check if there is a ring. 
 pos1 = head 
 pos2 = head 
 while pos2 and pos2.next: 
   pos1 = pos1.next 
   pos2 = pos2.next.next 
   l1 += 2 
   if pos2 and pos1 == pos2: 
     l2 = 1 
     break 
  ifnot l2: 
   if pos2: l1 += 1 
   return (l1, l2)  # l2 should be0 
 
  #Calc the length of the ring. 
 pos1 = pos2.next 
 while pos1 != pos2: 
   pos1 = pos1.next 
   l2 += 1 
 
  #Calc the length of the chain before the ring. 
  l1= 0 
 pos1 = head 
 pos2 = head 
  fori in xrange(l2): 
   pos2 = pos2.next 
 while pos1 != pos2: 
   pos1 = pos1.next 
   pos2 = pos2.next 
   l1 += 1 
 return (l1, l2)