链表经典题目——环形链表的数学证明

        环形链表是链表中的一个很经典的问题,很多人都知道要怎么解决这个问题,但是不知道其中的数学推理是怎么实现的;同时很多人也只知道要用快慢指针的方式,但是也不知道具体为什么是一个指针走一步一个指针走两步,那有没有可能出现链表成环但是却追不上的情况呢?同时,这个快慢指针是只能一个走一步一个走两步吗?三步、四部能不能达到我们想要的效果呢?接下来就一起用数学推理的方式来证明一下这些问题?

目录

        一、快慢指针为什么能证明链表带环

        二、当快慢指针的速度相差不为一时,是否会发生快慢指针永远不相遇的问题

        三、环形链表入口节点


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

  • 30
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值