题目描述
一个链表中包含环,请找出该链表的环的入口结点。
这题又用到了一快一慢两个指针的方法,快指针一次走两步慢指针一次走一步。可以证明,若存在环路,则这两个指针一定会在环路中某个地方相遇。这也是检测一个链表是否存在环路的方式。
接下来具体分析一下这两个指针什么时候会相遇。假设从第一个数据节点到环的入口节点需要走k步,那么当慢指针slow走到环的入口节点时,快指针fast已经走了2k步,其中在环内走了k步。k可能会比环路的节点数大会很多,快指针应该距离环的入口节点mod(k,LOOP_SIZE)步,记为K。
也就是说:当slow走到入口节点时,fast在环内走了K步。slow落后于fast,相距K。或者说fast落后slow,相距(LOOP_SIZE-K)步。
每走一次,fast都会靠近slow步。于是两者将在(LOOP_SIZE-K)步之后相遇,这个相遇点就距离环入口 K 步。
下面说一下怎么找到环路起始处。上面说了,在相遇点,还需走K步到环的入口节点 。
因为K=mod(k,LOOP_SIZE), k=K+M*LOOP_SIZE,所以也可以说从碰撞处,需要走k步到达环的入口节点(就是在环里面多绕几圈)。也就是说,链表第一个数据节点和碰撞节点都需要走k步到达环的入口节点。碰撞后,我们把一个指针指向链表第一个节点,一个指针指向碰撞处,以同样的速度移动,他们会在环的入口处相遇。
实现的Java代码如下(这儿的链表包含一个没有数据域的头结点):
public static ListNode entryNodeOfLoop2(ListNode head){
if(head==null||head.next==null)
return null;
ListNode slow=head.next,fast=head.next;
//找到碰撞处 ,处于链表中LOOPSIZE-k步
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if(fast==slow)
break;
}
//错误检查 没有环路
if(fast==null||fast.next==null){
return null;
}
//发生碰撞后,再将slow指向链表第一个数据点,fast流在碰撞处
//同速度走,相碰的地方就是 环路起始处
slow=head.next;
while(slow!=fast){
slow=slow.next;
fast=fast.next;
}
return fast;
}
剑指Offer上的实现,有些许不同。上面的的分析比较全面,分析后代码也更简洁。Offer中的方法更好理解,但是代码量稍多一点。各有优点,大家自己斟酌。Offer中的实现思路如下:
1.通过一快一慢两个指针找到碰撞处
2.遍历环,得到环中的节点数N
3.两个指针都初始指向第一个节点,然后第一个指针先走N步(两个指针相距就是环内节点数),然后一起走,第一次相遇的点就是环的入口。
public static ListNode meetingNode(ListNode head){
if(head==null||head.next==null)
return null;
ListNode slow=head.next,fast=head.next;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if(fast==slow)
return fast;
}
return null;
}
public static ListNode entryNodeOfLoop(ListNode head){
ListNode meetingNode=meetingNode(head);
if(meetingNode==null) return null;
//求环节点数目
int loopCount=1;
ListNode pNode=meetingNode;
while(pNode.next!=meetingNode){
pNode=pNode.next;
loopCount++;
}
//从第一个数据节点开始移动loopCount次
pNode=head.next;
for(int i=0;i<loopCount;i++){
pNode=pNode.next;
}
//同时移动
ListNode pNode2=head.next;
while(pNode!=pNode2){
pNode=pNode.next;
pNode2=pNode2.next;
}
return pNode;
}