一、如何判断单链表带环
方法:利用快慢指针
设置两个指针,一个名为fast,一个名为slow。两个指针开始指向链表头结点head。
fast一次走两步,slow一次走一步。
当fast进入环后,由于带环链表的next不指向空节点,fast指针一直在环内循环。
当slow也进入环后,fast指针和slow指针也在环内循环,当fast指针和slow指针在环内能够相遇时,表明该链表带环。
判断链表是否带环的函数代码如下:
bool hasCycle(struct ListNode *head)
{
struct ListNode*fast=head;
struct ListNode*slow=head;
while(fast && fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)
return true;
}
return false;
}
拓展思考
(1)slow一次走一步,fast一次走两步,fast指针会不会和slow指针一直错过,永远追不上呢?
(2)fast指针一次走三步呢?
一、
假设slow指针和fast指针一开始的距离为N,每次fast和slow指针走完后,fast指针和slow指针之间的距离减少1,即为N-1......到最后,fast指针和slow指针距离一定变为0,也就是说,只要slow一次走一步,fast一次走两步,到最后在环内必定能追上。
二、由一可知,如果fast指针一次走三步,那么fast指针和slow指针之间的距离每次减二,于是分为两种情况。
(1)N为偶数
那么到最后fast指针和slow指针必定能相遇
(2)N为奇数
那么fast指针在第一圈会超过slow指针一个节点,设整个环的长度为C,则两个指针此时的距离为C-1,如果C-1为偶数,则可以追上;否则C-1为奇数,两指针追不上。
但是,C为偶数,N为奇数导致两个指针永远错过这种情况真的存在吗?
假设带环链表之前的长度为L,fast在slow进环前已经转了x圈,此时两指针距离为N
那么slow走的路程为:L
fast走的路程为:L+x*C+N
因为此时fast指针走的路程是slow的三倍:L+x*C+N=3L
2L=x*C+N
此时N为奇数,C为偶数,2L必定为偶数,那么x*C必定为偶数,N为奇数,偶数加奇数必定为奇数,与2L必定为偶数不符,所以这种情况不存在,也就是说slow一次走一步,fast一次走三步也能相遇。
二、在带环链表中寻找第一个入环的节点
在上图,我们假设slow和fast指针相遇的节点为meet指针,头结点为head指针。(fast指针一次走两步,slow指针一次走一步)
先说方法:meet指针和head指针一次走一步,最后相遇的时候所处的节点就是第一个入环的节点
解析
设meet节点与第一个环节点距离为N,头结点与第一个环节点距离为L,整个环的长度为C。
此时slow指针走的路程为:L+N(注意:当slow和fast指针相遇时,slow指针必定只走了一圈之内。因为slow指针如果走了超过一圈,fast指针必定走了超过两圈,一定在一圈之内就追上了)
那么假设fast指针走了x圈,此时fast指针走的路程为:L+x*C+N
此时fast路程为slow两倍:L+x*C+N=2L+2N
L=(x-1)*C+(C-N)
由这个关系式可知:meet指针走的步数是C-N加上环总长度的整数倍,由于环的整数倍长度的影响可以直接忽略,所以可以知道,head指针和meet指针一次走一步,最后必定在第一个入环节点相遇。
代码如下:
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode*fast=head;
struct ListNode*slow=head;
struct ListNode*meet;
if(head==NULL || head->next==NULL)
return NULL;
else
{
while(fast && fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)
break;
}
if(slow!=fast)
return NULL;
meet=slow;
while(meet!=head)
{
head=head->next;
meet=meet->next;
}
return meet;
}
}