每天一道LeetCode-----判断链表是否有环,如果有,找到环的入口位置
LeetCode原题链接:
题目链接
思路
1. 判断一个链表是否有环,空间复杂度是O(1)
如果不考虑空间复杂度,可以使用一个map记录走过的节点,当遇到第一个在map中存在的节点时,就说明回到了出发点,即链表有环,同时也找到了环的入口。
不适用额外内存空间的技巧是使用快慢指针,即采用两个指针slow和fast,slow每次移动一步而fast每次移动两步。当slow和fast第一次相遇时,证明链表有环
如果链表中有环,那么当慢指针进入到环时,在未来的某一时刻,快慢指针一定可以相遇,通过这个也就可以判断链表是否有环
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
auto slowPtr = head;
auto fastPtr = head;
while(fastPtr && fastPtr->next)
{
slowPtr = slowPtr->next;
fastPtr = fastPtr->next->next;
if(slowPtr == fastPtr)
return true;
}
return false;
}
};
2. 找出环的入口点(起点)
当fast按照每次2步,slow每次一步的方式走,发现fastPtr和slowPtr重合,确定了单向链表有环路。接下来,让slowPrt回到链表的头部,然后slowPtr和fastPtr各自从自己的位置(fastPtr从两个指针相遇的位置position出发)沿着链表出发,每次步长1,那么当fastPtr和slowPtr再次相遇的时候,就是环路的入口了。
论证过程参考(请点击链接)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
auto walker = head;
auto runner = head;
while(runner && runner->next)
{
walker = walker->next;
runner = runner->next->next;
if(walker == runner)
break;
}
if(!runner || !runner->next)
return nullptr;
auto headWalker = head;
auto crossWalker = walker;
while(headWalker != crossWalker)
{
headWalker = headWalker->next;
crossWalker = crossWalker->next;
}
return headWalker;
}
};
3. 衍生问题:求环的长度
当fast按照每次2步,slow每次一步的方式走,发现fastPtr和slowPtr重合,确定了单向链表有环路。接下来,让slowPrt不动,fast 绕着环移动,每次移动一步,计数count加1,当两指针再次相遇时,count即是环的大小
第一种方法是利用上面求出的环入口,再走一圈就可以求出长度,代码如下
int cycleLen(ListNode* head)
{
auto cycleIn = detectCycle(head);
int len = 1;
auto walker = cycleIn;
while(walker->next != cycleIn)
{
++len;
walker = walker->next;
}
return len;
}
第二种方法是当快慢指针相遇时,继续移动直到第二次相遇,此时快指针移动的距离正好比慢指针多一圈,代码如下
int cycleLen(ListNode* head)
{
auto walker = head;
auto runner = head;
while(runner && runner->next)
{
walker = walker->next;
runner = runner->next->next;
if(walker == runner)
break;
}
int len = 0;
while(runner && runner->next)
{
++len;
walker = walker->next;
runner = runner->next->next;
if(walker == runner)
break;
}
return len;
}
第三种方法是当快慢指针相遇时,其中一个指针不动,另一个指针每次移动1,当第二次相遇,移动的距离就是环的长度,代码如下
int cycleLen(ListNode* head)
{
auto walker = head;
auto runner = head;
while(runner && runner->next)
{
walker = walker->next;
runner = runner->next->next;
if(walker == runner)
break;
}
int len = 0;
while(1)
{
++len;
walker = walker->next;
if(walker == runner)
break;
}
return len;
}