循环链表:如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。
方法:用快慢指针slow和fast,slow和fast指向链表的开始,slow一次走一步,fast一次走两步。不带环,fast就会为空;带环,fast就会在环里追上slow。
延伸问题:1、为什么slow和fast一定会在环中相遇?会不会在环里错过,永远遇不上?证明。
2、为什么slow走一步,fast走两步,能不能fast走一次走n步?证明一下。
第一步:slow和fast,fast一定是先进环,这时slow走入了环前距离的一半;
第二步:随着slow进环,fast已经在环里走了一段,走了多少跟环的大小有关系。假设slow进环的时候,slow跟fast的距离是N,fast开始追slow,slow每往前走一步,fast往前走两步,每追一次,判断一下相遇,每追一次,fast和slow的距离变化:N,N-1,N-2,...0。每追一次,距离 减少1,他们之间的距离减少到0的时候就是相遇的点。
假设:slow一次走一步,fast一次走三步,slow进环后,fast跟slow之间的距离为N,fast开始追slow,他们之间的距离变化如下
如果N是奇数,距离变成-1意味着他们之间的距离变成C-1(C是环的长度),如果C-1是奇数,就永远追不上了;如果C-1是偶数,就可以追上。因此只能slow走一步,fast走两步,不能fast走一次走n步。
slow走一步,fast走两步,一定会相遇。如何求环的入口点?结论:一个指针从相遇点开始走,一个指针从链表头开始走,他们会在环的入口点相遇。
追上相遇的过程中,慢指针走的距离:L+X,。快指针走的距离:L+N*C+X,N(N>=1)是他们相遇之前,fast在环里走的圈数。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
if(pHead == NULL || pHead->next == NULL)
return NULL;
struct ListNode* slow=head,*fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
{
struct ListNode *meet=slow;//相遇点
while(meet!=head)
{
meet=meet->next;
head=head->next;
}
return meet;
}
}
return NULL;
}
例题
1.环形链表(力扣)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
struct ListNode* slow=head,*fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
{
return true;
}
}
return false;
}