题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。如图所示,节点3是链表中环的入口节点
本题的解决分为两步:(1)确定链表中是否有环(2)如果有环,确定环的入口节点。我们使用双指针法解决此问题
第一步:确定链表中是否有环(快慢指针)
我们使用快慢指针法,它是双指针的一种形式。定义两个指针p1、p2,起始点都在链表的头结点;p1是慢指针每次向前走一步,p2是快指针每次向前走两步;若不存在环,p2会率先到达尾结点的next,即null,此时便可以返回null;若存在环,p2会率先进入环并在环中做循环运动,p1后进入环中,而后p2总会在环中的某一点追上p1;确定有环;
在上图的链表中,p2会在节点5处被p1追上
第二步:确定环的入口节点(双指针法)
这里我们使用上一步的两个指针
要确定环的入口节点,首先要知道环的长度。如上图中的链表,我们从p1、p2相遇的节点5开始,p1再向前走4步会再次回到节点5,由此确定了环的长度为4
已经确定了环的长度n。让p1、p2都回到链表头结点;然后p1不动,p2先向前走n步;接下来p1和p2均一次走一步,直到p1和p2相遇,相遇点就是环的入口点。那么如何证明这一点呢?
证明:
假设链表非环部分长为l,环部分长为n
若l==n,p2先走n=l步到达环的入口;p1走到l步到达环的入口处时p2恰好走n步,绕环一圈回到环的入口与p1相遇
若l<n,p2先走n步,距离环的入口还有l-n;p1走l步到环的入口时,p2也走了l步,因为n+l-n=l,所以此时p2也绕环一圈回到环的入口和p1相遇
若l>n,p2先走n步,已经进入了环n-l步,距离下一次到达环入口还有l步;p1走l步到环的入口时,p2也走l步到达环的入口与p1相遇
因此,当p2先走环的长度步,p1、p2再开始以相同速度前进,p2总会在绕环一周后与p1在环的入口相遇
解题代码:
class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead==null)
{
return null;
}
ListNode p1=pHead,p2=pHead;
while(p2!=null) //确定是否有环,p1慢指针,p2快指针
{
p1=p1.next;
if(p2.next!=null && p2.next.next!=null)
{
p2=p2.next.next;
if(p1==p2) //再次相遇,链表中有环
{
int lenCircle=1;
p2=p2.next;
while(p2!=p1) //计算环的长度
{
p2=p2.next;
lenCircle++;
}
p1=pHead;
p2=pHead;
for(int i=0;i<lenCircle;i++) //p2向前走环长度的步数
{
p2=p2.next;
}
while(p1!=p2)
{
p1=p1.next;
p2=p2.next;
}
break; //p1、p2再次相遇点就是环的入口
}
}
else
{
return null;
}
}
return p2;
}
}
时间复杂度:O(n)
空间复杂度:因为只使用了两个指针,因此是O(1)