环形链表是链表中的一个很经典的问题,很多人都知道要怎么解决这个问题,但是不知道其中的数学推理是怎么实现的;同时很多人也只知道要用快慢指针的方式,但是也不知道具体为什么是一个指针走一步一个指针走两步,那有没有可能出现链表成环但是却追不上的情况呢?同时,这个快慢指针是只能一个走一步一个走两步吗?三步、四部能不能达到我们想要的效果呢?接下来就一起用数学推理的方式来证明一下这些问题?
目录
二、当快慢指针的速度相差不为一时,是否会发生快慢指针永远不相遇的问题
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {
if(head==NULL || head->next == NULL)
{
return false;
}
else
{
ListNode *fast=head->next;;
ListNode *slow=head;
while(slow!=fast)
{
if(fast ==NULL || fast->next == NULL)
{
return false;
}
slow=slow->next;
fast=fast->next->next;
}
return true;
}
}
一、快慢指针为什么能证明链表带环
首先我们设有一个带环链表,有两个指针分别指向链表的第一个节点,fast指针每次走两步,slow每次走一步,链表的不带环部分的长度为d,这是链表的初始状态
当fast指针走到环的入口时,因为fast指针的速度是slow的两倍是,所以此时slow指针还在不成环的部位
当slow指针走到环的入口时,此时fast指针肯定已经在成环部分中了fast和slow指针之间的距离为N
当slow刚进环时,两个指针之间的距离是N,因为fast的速度是slow的两倍,所以每走一步,这两个之间距离就会以每次缩短一的速度减小
当这两个指针相遇,则可以证明这个链表带环,若这个链表不带环,快指针一定会比慢指针先走到链表的尾节点,从而证明出链表不带环
二、当快慢指针的速度相差不为一时,是否会发生快慢指针永远不相遇的问题
这里拿快指针的速度是慢指针的三倍来举例子,同理我们设当slow指针到环形部分入口时slow和fast指针之间的距离为N,链表环形部分的长度为C
当slow和fast指针每走一次时,两个链表的距离会以每次2的速度减少,此时就要分N是奇数和N是偶数的情况来分别进行讨论了
当N时偶数的时候,两个指针就会直接相遇,当N是奇数的时候,两个指针就会正好错过,此时就要根据环形的长度C来讨论了,因为当N的长度是奇数时,两个指针正好错过,此时两个指针的距离就是C-1
由此可见,当C-1是偶数时,即C是奇数,两个指针还是能够相遇的;当C-1时奇数时,即C是偶数时,两个指针正好会错过
由此我们可以得出结论,当slow指针进入环形部分入口时和fast指针之间的距离N为奇数,同时环形部分的长度为偶数时,会发生两个指针永不相遇的问题。
但是,长度C和距离N真的能够同时为偶数吗?
通过前面的假设我们可以知道链表不带环的部分是d,带环部分的长度是C,当slow指针走到环形部分入口时和fast指针之间的距离为N,关系如下图
当slow指针走到这个位置时,slow指针走过的长度为d,因为不知道环的长度C具体是多少,所以fast指针可能已经在环形部分中走了x圈再走到这个位置;因为fast指针的速度是slow指针的三倍,所以可以得到一个等式 3*d = d + C*x + C - N,化简可得2d = (x+1)* C-N,因为2d是一个偶数,当C是一个偶数所以(x+1)* C也是一个偶数,偶数只能通过两个奇数相减或者是两个偶数相减才能到一个偶数,所以当C是偶数时,N不能为奇数,因此不存在C为偶数同时N为奇数的情况,所以可以得出结论,两个指针不存在不相遇的情况,所以fast指针时slow指针速度的三倍也永远可以证明链表带环的问题。
三、环形链表入口节点
这个问题也是链表中的经典问题,就是要找到一个带环链表的环形部分入口。
设有一个slow指针一次走一步,还有一个fast指针一次走两步,这个带环链表不带环部分长度为d,带环部分长度为C当slow指针第一次和fast指针相遇时,和环形部分入口直接的距离为N如图所示
fast指针在和slow指针相遇前,可能已经在环形中绕了x圈,fast指针要追上slow指针的话,fast至少需要再环内走一圈,所以x是大于1的,又因为fast指针的速度是slow指针的两倍,所以可以得到一个等式 2*(d+N) = d +C*x +N 化简可得 d = (x-1)*C-N,此时fast指针和slow指针所在的位置离环形部分的入口位置的距离也正好是C-N,所以此时只需要,让一个新的指针从头开始一次走一步,让slow指针从当前位置继续一次走一步,当两个指针相遇时,相遇的这个位置就是环形部分的入口位置。具体代码如下
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
ListNode *slow,*fast;
slow=fast=head;
do
{
if (fast == NULL || fast->next == NULL)
return NULL;
else
{
slow = slow->next;
fast = fast->next->next;
}
} while (fast != slow);
fast=head;
while(fast !=slow)
{
slow=slow->next;
fast=fast->next;
}
return fast;
}