弗洛伊德的兔子与乌龟(Floyd‘s Tortoise and Hare algorithm)

目录

问题引入

floyd算法介绍

算法解释

代码

兔子速度和乌龟速度

环的入口题目引入

数学公式

解法与代码(一)

解法与代码(二)

相交问题地址及代码

brent算法介绍

brent代码


问题引入

当有一条链表,你需要判断其是否带环:

或者

你会怎么做?

具体题目如下

题目地址:141. 环形链表 - 力扣(LeetCode)

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;
}

解法与代码(二)

此时我们可以将相遇结点和其下一个结点的联系断开,此时就变成了链表相交结点的问题

相交问题地址及代码

160. 相交链表 - 力扣(LeetCode)

/**
 * 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;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值