这道题目是141. Linked List Cycle的加成版https://leetcode.com/problems/linked-list-cycle/,思路都是“龟兔赛跑追及问题必相遇”,但这条需要确定环的入口节点。我们需要定量化一下:
pSlow是慢指针,pFast为2倍速指针
当pSlow经过i(i=4)“单位步长”首次到达Entry节点时,pFast已经走了8(2i=8)单位步长(如图)
假设某个时刻相遇在Hit节点,假设顺时针方向Entry节点到Hit节点有b单位步长,Hit到Entry节点有c单位步长,环的长度为L=b+c,pSlow走过的单位步长为i,pSlow在环中走了 圈, ,因为运动时间相同速度二倍的关系pFast走过的步长为2i, pFast在环中走了 圈, ,则有下式:假设某个时刻相遇在Hit节点,假设顺时针方向Entry节点到Hit节点有b单位步长,Hit到Entry节点有c单位步长,环的长度为L=b+c,pSlow走过的单位步长为i,pSlow在环中走了 圈, ,因为运动时间相同速度二倍的关系pFast走过的步长为2i, pFast在环中走了 圈, ,则有下式:
代码如下:
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head==null || head.next==null)
return null;
if(head.next==head)
return head;
ListNode pFast=head;
ListNode pSlow=head;
while(true)
{
if(pFast.next==null || pFast.next.next==null)
return null;
pFast=pFast.next.next;
pSlow=pSlow.next;
if(pFast==pSlow)
{
pSlow=head;
while(pFast!=pSlow)
{
pFast=pFast.next;
pSlow=pSlow.next;
}
return pFast;
}
}
}
}
}
速度还是蛮快的。
在wikipedia上查了cycle detection(https://en.wikipedia.org/wiki/Cycle_detection)的问题里面的理论阐释蛮多的。wiki介绍了还有一个Brent算法,代码如下:
public class Solution {
public static ListNode detectCycle(ListNode head){
if(head==null || head.next==null)
return null;
int power=1;
int lambda=1;
ListNode pSlow=head;
ListNode pFast=pSlow.next;
while(pSlow!=pFast)
{
if(power==lambda)
{
pSlow=pFast;
power*=2;
lambda=0;
}
if(pFast==null)
return null;
pFast=pFast.next;
lambda+=1;
}
pSlow=head;
pFast=head;
for(int i = 0;i < lambda;i++)
pFast = pFast.next;
while(pSlow != pFast)
{
pSlow = pSlow.next;
pFast = pFast.next;
}
return pSlow;
}
}
可耻的翻译了一段wiki原文(勿喷):
2倍速指针的妙处!!!
最早遇到这个问题是在刷LeetCode-287. Find the Duplicate Number:https://leetcode.com/problems/find-the-duplicate-number/看到了有趣的龟兔赛跑的算法,感觉惊为天人的tricks,这大概就是算法之美吧,美得令人陶醉,就是朴素的让人看到思路就想去写出代码的额冲动!
那个题目将指针换成了数组索引,但思路就是双指针追及,代码如下:
class Solution {
public int findDuplicate(int[] nums) {
int pFast=0,pSlow=0;
while(true)
{
pSlow=nums[pSlow];
pFast=nums[nums[pFast]];
if(pSlow==pFast)
{
pFast=0;
while(pFast!=pSlow)
{
pFast=nums[pFast];
pSlow=nums[pSlow];
}
return pFast;
}
}
}
}