判断链表是否有环方法——快慢指针
一、 问:如何判断是否有环?
答:如果有两个头结点指针,一个走的快,一个走的慢,那么若干步以后,快的指针总会超过慢的指针一圈。
设置两个指针(fast,slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)。
#include <stdio.h>
typedef struct ListNode
{
int val;
ListNode *next;
};
//判断链表是否有环(C++实现)
bool isloop(ListNode *pHead)
{
ListNode *fast=pHead;
ListNode *slow=pHead;
//链表长度为奇数,则fast->next为空,否则,fast为空
while(fast!=NULL&&(fast->next!=NULL))
{
fast=fast->next->next;
slow=slow->next;
//如果有环,则两个指针在环中某一点相遇
if(fast==slow)
{ break; }
}
if(fast==NULL||fast->next==NULL)
return false;
else
return true;
}
附其他方法:
法1,遍历链表。
将遍历过的节点放在一个hash表中。如果一个节点已经存在hash表中,说明链表有环。时间复杂度O(n),空间复杂度O(n)。
法2,反转链表。
二、计算环的长度
//计算环的长度:从第一次相遇开始计数,到第二次相遇,走的长度即为环的长度
int len_loop(ListNode *pHead)
{
if(isloop(pHead)==false)
return 0;
ListNode *fast=pHead;
ListNode *slow=pHead;
int len=0;
bool begin=false;
bool again=false;
while(fast->next!=NULL&&fast!=NULL)
{
fast=fast->next->next;
slow=slow->next;
//超一圈后开始计数
if(fast==slow&&(again==false))
{
begin=true;
again=true;
}
//超过两圈停止计数,跳出循环
if(fast==slow&&(again==true))
break;
//计数
if(begin==true)
++len;
}
return len;
}
三、问:如何判断环的入口点?
答:碰撞点p到连接点的距离=头指针到连接点的距离,所以,分别从碰撞点、头指针开始走,相遇的那个点就是连接点。
为什么呢?需要一个简单的计算过程:
(1)当fast与slow相遇时,show肯定没有走完链表,而fast已经在还里走了n(n>= 1)圈。假设slow走了s步,那么fast走了2s步。fast的步数还等于s走的加上环里转的n圈,所以有:
2s = s + nr。因此,s = nr。
(2)设整个链表长为L,入口据相遇点X,起点到入口的距离为a。因为slow指针并没有走完一圈,所以:
a + x = s,带入第一步的结果,有:a + x =nr = (n-1)r + r = (n-1)r + L - a;即:
a = (n-1)r + L -a -x;
这说明:从头结点到入口的距离,等于转了(n-1)圈以后,相遇点到入口的距离。因此,我们可以在链表头、相遇点各设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。
//计算环的入口结点
Node* findLoopEntrance(ListNode *pHead)
{
ListNode *fast=pHead;
ListNode *slow=pHead;
while(fast->next!=NULL&&fast!=NULL)
{
fast=fast->next->next;
slow=slow->next;
//到达相遇点
if(fast==slow)
{ break; }
}
if(fast==NULL||fast->next==NULL)
return NULL;
slow=pHead;//slow指针从头结点开始遍历
while(slow!=fast)
{
slow=slow->next;
fast=fast->next;
}
return slow;
}
附:
如何判断两个链表(不带环)是否相交?将其中的一个链表首尾相连,然后判断另一个链表是否带环即可,而检测出来的依赖环入口即为相交的第一个点。