Leetcode-141.环形链表(龟兔赛跑算法)

在这里插入图片描述
这种可能带环的链表是比较麻烦的,要判断链表中是否有环。
我看过设置一个标记来断开一个环的算法(虽然我觉得没什么意义),两者是完全不一样的。这道题以我目前的水平不会使用一些高级的数据结构,只能通过一个算法来解答–弗洛伊德循环查找算法(龟兔赛跑算法)。
接下来,我会通过一些数学推理来验证该算法。

该算法的实现底层是快慢指针。而对于快慢指针而言,通常我们会设计快指针一次走两个结点,而慢指针一次走一个结点,这样保险一点且不会错过结点。至于为什么,我会在下面的验证过程中得出。

为什么会认为快慢指针就能得出链表中是否有环呢?

因为,如果链表中没有环时,fast会走到NULL,就可以得出链表无环,因为会结束,有NULL。
而有环时,快慢指针最终会指向同一个地址,至于为什么,请看证明。

对龟兔赛跑的推理验证

在这里插入图片描述

我们假设在线上(环外)有K+1个结点(head刚开始就是他们所在的位置)。环中有N个结点。

当fast进入结点时,走了K个结点,而slow走了K/2个结点(由于未知K的奇偶,只是初略描述,后面会更加细节)。

而当slow走到环的入口时进入环,走了K/2个结点,fast走了K个结点,在环内走了K个结点。

而此时fast距环的入口有K%N个结点。而slow是在环的入口。则这意味着两个指针相距K/N个结点。

情况1
在这里插入图片描述

情况2
在这里插入图片描述

情况3
在这里插入图片描述
对于情况3而言,当慢指针进环时,快指针已经绕环走了n圈,两者恰好走到了一起。
也可能情况1情况2都是已经走了n圈后的最后情况(千万不要被图给误导了,一切还是要看数据说话)。

这三种情况都是要考虑环外结点与环内结点的数目。

这些都是当慢指针入环的情况,还是要看看之后的步骤。
因为快指针一次走两个结点,而慢指针一次只走一个结点,所以肯定是fast去追slow。
对于上面三种情况而言,
在这里插入图片描述
在这里插入图片描述
绿色的路径才是fast要追slow的结点。而这部分节点数为N-K%N。我们设M=N-K%N。即,在刚开始时,fast追slow,要追M个结点,两者相差M个结点。两者再走一次,

fast走两个结点
slow走一个结点
两者相差M-2+1=M-1
相差M-1个结点

再走一次

ast走两个结点
slow走一个结点
两者相差M-1-2+1=M-2
相差M-2个结点

继续

相差M-3
相差M-4


这就相当于每两结点走一次,两个的距离就会缩小一个结点。
从某种意义上,相当于slow没动一直是fast一次一个结点的运动。
两指针继续走直到


相差1个结点,
相差0个结点。

两指针就走到一块了。即可证明有环。

至于为什么要一般让快指针一次走两个结点。再看。

如果快指针一次走3个结点?一次走4个结点呢?情况会怎样?

前面的步骤差不多,主要还是在环内的推理分析。
在这里插入图片描述
还是假设两指针相差M个结点。

1,相差M-3+1=M-2
2,相差M-2-3+1=M-4
3,相差M-4-3+1=M-6





C-2,相差M-2*(C-2)
C-1,相差M-2*(C-1)
走了a次后相差M-2*a个结点

就相当于,慢指针不动,快指针一次走两个结点。但是两者到底能不能指向同一个地址还要看M-2*a的值。

而,当值为0时,意味着,两指针指向同一处。但M-2*a的值还是得看M的值,如果是2的倍数才能等于0,如果不是,那么将不会存在等于0的时候,即两指针就不会指向同一个地址。

而M=2K%N(因为fast的速度是slow的3倍)。如果怀疑可能会在下一次的时候一定相遇,那我们就来判断。

则继续循环,而此时两指针相距
在这里插入图片描述

N-abs(M-2 * a)。
等于N-abs(2K%N-2 * a)。
随着循环继续,两指针差距为,{【N-abs(2K%N-2 * a)】-(2 * b)}。
假设差距能等于0,即N-abs(M-2a)为2的倍数,这全取决于.
对于M而言,诺M为奇数,则第一次是不会相遇
而第二次,取决于N-(M-2
a).
而该值是与环的节点数密切相关。是否可以快慢指针重合,全与N有关。且,第一次无法重合,即M-2*a就也会是一个奇数(不可能是偶数)。
而,诺N也是奇数,即结果就会是一个偶数,即第二次就会相遇,诺是偶数,即第二次是无法相遇。

是否相遇全取决于N与M。当快指针一次走3步,或4步的时候。就容易出现无法重合的问题。

所以在大多数情况下,快指针一般是走两步,比慢指针快一个结点。

龟兔赛跑算法实现

针对题目的算法实现

bool hasCycle(struct ListNode *head) 
{
    struct ListNode* fast=head,*slow=head;
    int i=0;
    while(fast&&fast->next)
    {
        
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow)
        {
            return 1;
        }
    }
    return 0;
}

快慢指针,依次循环遍历。
如果fast找到NULL就意味着无环结构。
否则就继续循环遍历,直到快慢指针指向同一地址,就结束函数,同时返回1,代表链表中有环结构。
在循环条件中,设置一个fast->next,是为了防止指针越界。毕竟快指针要走两个结点。

感谢观看,我知道在上面推理上数学验证是不严谨的,由于我目前数学水平不够。日后会对该算法有完整的推理。
谢谢大佬观看

### 回答1: 好的,我来用中文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的题目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问题,比如最长公共子序列、背包问题、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的题型,很多题目都需要用到动态规划的思想来解决。 这个链接里包含了很多关于动态规划的题目,按照难度从简单到困难排列。每个题目都有详细的题目描述、输入输出样例、题目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提高自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问题和最优子结构性质的问题。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问题,如背包问题、图像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统中,动态规划也广泛应用于各种优化算法中,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,题目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法LeetCode的动态规划标签下的题目涵盖了各种难度级别和场景的问题。从简单的斐波那契数列、迷宫问题到可以用于实际应用的背包问题、最长公共子序列等,难度不断递进且话题丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的题目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问题拆分成子问题的方式进行求解。在LeetCode中,动态规划标签涵盖了众多经典和优美的算法问题,例如斐波那契数列、矩阵链乘法、背包问题等。 动态规划的核心思想是“记忆化搜索”,即将中间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程中的中间值,例如动态规划求解斐波那契数列问题时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列中,第j个元素的值。 在LeetCode中,动态规划标签下有众多难度不同的问题。例如,经典的“爬楼梯”问题,要求我们计算到n级楼梯的方案数。这个问题的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问题还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问题、保存中间状态来求解问题。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问题,但在某些场景下并不适用。例如,某些问题的状态转移过程比较复杂,或者状态转移方程中存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解题都非常重要。除了刷题以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法数据结构基础》等,来深入理解这种算法思想。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值