目录
问题引入
当有一条链表,你需要判断其是否带环:
或者
你会怎么做?
具体题目如下
floyd算法介绍
创造两个指针,一个快指针,一个慢指针。顾名思义,快指针的速度要比慢指针块,这也是为什么它也被称作龟兔算法。兔子是快指针,乌龟是慢指针,每当乌龟走一步,兔子就走两步。
当兔子走到空的时候,证明该链表不存在环。
如果链表存在环,那么兔子和乌龟终会在环内相遇。
算法解释
为什么当兔子速度是乌龟的两倍时,兔子能刚刚好追上乌龟呢?
假设当乌龟来到入环结点时,兔子此时与乌龟的距离为N
当乌龟走1步,兔子走2步时,它们之间的距离为N+1-2=N-1
当乌龟再走1步,兔子也再走两步时,它们之间的距离为N+1-2=N-2
发现了吗?它们之间的距离在慢慢缩小,总有一天这个距离会减为0,那时就是它们相遇的时候了。
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode SL;
bool hasCycle(struct ListNode *head) {
SL* fast=head;
SL* slow=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
return true;
}
return false;
}
兔子速度和乌龟速度
你是否想过这么一个问题?
当兔子的速度不再是乌龟的2倍,而是3倍、4倍、5倍、甚至k倍呢?
这个问题我们用k为3来举例子,剩下的归于数学归纳法。
设环的长度为C
首先我们假设入环时乌龟已经走了L,那么兔子此时可能已经走了n圈,并且离乌龟的距离为N,那么兔子此时走的路程为L+n*C+C-N
当兔子的速度是乌龟的3倍,它们的速度差为2,即每当乌龟走一步,兔子走三步,它们之间的距离减小二。
1.当N%2==0,即能够追上
2.当N%2==1,此时兔子会超过乌龟,它们之间的距离会变成C-1
此时因为N%2==1,即N为奇数。它们之间的距离变为C-1,
2.1
若(C-1)%2==0,即追得上
2.2
若(C-1)%2==1,C为偶数,此时每追击一轮它们之间的距离就会变成C-1,永远错过。但是这种情况是不可能的的,我们来看:
这种情况是C为偶数,N为奇数
当乌龟刚入环时,它所走的长度为L
兔子所走的长度为L+n*C+C-N
又因为兔子的速度是乌龟的3倍,即L+n*C+C-N=3*L,化简得2*L=(n+1)*C-N
∵C为偶数,那么(n+1)*C也为偶数,N为奇数,∴(n+1)*C-N的结果必须是奇数,不可能是2*L。即C为偶数,N为奇数的情况不存在。
所以最后兔子和乌龟一定会相遇。
环的入口题目引入
题目地址:142. 环形链表 II - 力扣(LeetCode)
基于上面的是否带环链表问题我们产生了一个新的问题
如果我们已经确定链表是带环的,怎么求出环入口结点呢?
首先我们将入环前的链表长度设为X
环长度为L
将相遇点到入环结点的距离设为N
将入环结点到相遇点的距离设为Y,而Y=L-N
数学公式
首先从乌龟进环开始,兔子一定能在一圈内追上乌龟。因为兔子的速度是乌龟的两倍,它们之间的距离N<L环的长度,乌龟每走一步,它们之间的距离就-1,所以兔子一定能在乌龟再次到达环入口之前追上乌龟。
f:兔子
s:乌龟
相遇时//此时假设兔子已经在环内走了c圈
f=x+cL+y
s=x+y
2s=f
2x+2y-x-y=cL
x=cL-y
x=(c-1)L+N
即当乌龟走了x,来到环入口,这个距离=兔子走了(c-1)个圈+N
解法与代码(一)
我们用一个指针p记录相遇结点,再用一个指针x位于开头
两个指针同时走,当它们相等时,指针所指的结点就是环入口节点
typedef struct ListNode SL;
struct ListNode *detectCycle(struct ListNode *head) {
SL*fast=head;
SL*slow=head;
SL*meet=NULL;
SL*pshead=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
{
meet=slow;
}
while(meet&&meet!=pshead)
{
pshead=pshead->next;
meet=meet->next;
}
if(meet)
{
return meet;
}
}
return NULL;
}
解法与代码(二)
此时我们可以将相遇结点和其下一个结点的联系断开,此时就变成了链表相交结点的问题
相交问题地址及代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode SL;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if (headA == NULL || headB == NULL)
{
return NULL;
}
int counta = 0;
int countb = 0;
SL* Atail = headA;
SL* Btail = headB;
SL* psa = headA;
SL* psb = headB;
while (Atail->next)
{
Atail = Atail->next;
counta++;
}
while (Btail->next)
{
Btail = Btail->next;
countb++;
}
if(Atail==Btail)
{
int k = abs(counta - countb);
while (k--)
{
if (counta > countb)
{
psa = psa->next;
}
else
{
psb = psb->next;
}
}
while (psb != psa)
{
psb = psb->next;
psa = psa->next;
}
return psb;
}
else
return NULL;
}
brent算法介绍
相对于floyd,brent算法中,兔子在继续移动,但是乌龟是静止的。每当兔子走了一轮后,乌龟传送到兔子的位置。
第一轮:乌龟不动,兔子走一步
第二轮:判断乌龟和兔子是否相遇,不相遇乌龟来到兔子位置,兔子走两步
第三轮:判断乌龟和兔子是否相遇,不相遇乌龟来到兔子位置,兔子走四步
...
第n轮:判断乌龟和兔子是否相遇,不相遇乌龟来到兔子位置,兔子走2^n步
brent代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode LN;
bool hasCycle(struct ListNode *head) {
LN*fast=head;
LN*slow=head;
int i=1;
int count=0;
while(fast&&fast->next)
{
fast=fast->next;
count++;
if(slow==fast)
{
return true;
}
if(count==i)
{
count=0;
i=i*2;
slow=fast;
}
}
return false;
}