判断单链表是否带环并且返回首个环节点和相应数学原理

问题是,给你一个单链表判断这个链表是否带环,如果带环返回这个链表的环的首节点,如图

由于链表带环,所以难以直接确定链表的长度,所以无法遍历链表来看是否带环

所以使用快慢两个指针来判断是否带环,如图

快指针每次走两个节点,

慢指针每次走一个节点

如图

当快指针指向第三个节点时,慢指针走到第二个节点

当慢指针走到第三个节点时,快指针刚好转了一圈换回到第三个节点,

此时比较两个指针的地址,如果两个指针的地址相同,则说明带环

如果快指针已经为空,说明遍历链表,则不带环

 代码实现如下(只判断是否带环)

struct ListNode
{
    int val;
    struct ListNode* next;
};
bool hasCycle(struct ListNode* head)
{
    if (head == NULL)
    {
        return false;
    }
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while (fast->next)
    {
        if (fast->next->next == NULL)//快指针一次前进两个节点
        {
            return false;
        }
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)//不能比较val要比较地址,否则有相同的val会出错
        {
            return true;
        }
    }
    return false;
}

那么问题是

如何证明(环在任意位置,环的大小任意时)

快指针一次走两个节点一定可以和慢指针相遇

同理,快指针一次走3个节点,慢指针一次走一个节点是否一定相遇

同理,快指针一次走n个节点,慢指针一次走一个节点是否一定相遇

给同理,快指针一次走n个节点,慢指针一次走m个节点是一定否相遇

首先第一个问题,快指针两次走一个节点是否一定与慢指针相遇

如图

无论环的起点和环的大小快指针必然先进入环

而当慢指针到达环的起点时,快指针必然在环的任意位置,两者间的距离我们记作X

当X==0,直接相遇

当X==1,下次相遇

当X>1, X减1

无论起始位置X和环的大小X每次减1必定刚好减为0

所以fast与slow必然相遇

而当快指针一次走3个节点,满指针一次走一个节点

当X==0,直接相遇

当X==1,则追击距离改为Y-1

追前:

追后

 

 

当X==2, 下一次相遇

当X>2,X减2

综上,因为每次减2,

所以当X为奇数时X不会减到0

当X为奇数且Y-1也为奇数时,X永远减不到0

由上可以发现,每次追击X减少(快指针一次走的节点(N)和慢指针一次走的节点(M)的差)

即 一次追击后, X == X - (N - M)

当X == N-M时,下次追上

当N-M>1时则

{       当 X == N - M -1 追击距离变为Y-1 (num==1)

        当 X == N - M -2 追击距离变为Y-2(num==2)

         当 X == N - M -3 追击距离变为Y-3(num==3)

         .......

        直到追击距离变为0或者num+M>N

}

所以

当快指针一次走N次慢指针一次走1次

或者当快指针一次走N次慢指针一次走M次

必然不一定相遇

最后一点,如何返回带环链表的环首节点

有两个方法

第一个是公式法

第二个是把这个问题转换为双链表求交点问题

第一种方法

如图,

 

我们设环节点的首节点距离链表头节点的节点数为L

设环的大小为C

slow指针与fast指针的相遇点距离环首节点的距离为X(X必然小于环的大小,圈一必定追上,最坏的情况为C-1次追上)

设慢指针走到环的首节点时,快指针走了N圈

则有(快一次走2节点,慢一次走1节点)

当快慢指针相遇时

慢指针走过的距离 == L + X

快指针走过的距离 == L + N * C + X

2 *(L + X)== L + X + N * C 

(L + X) == N * C

L == N * C - X

L == (N - 1) * C + C - X //这个等式说明一个指针从相遇点出发,一个指针从链表头节点出发,他们将在环的首节点相遇

如图

 

代码实现如下

struct ListNode *detectCycle(struct ListNode *head) 

{

    if (head == NULL)

    {

        return NULL;

    }

    struct ListNode* slow = head;

    struct ListNode* fast = head;

    int flag = 0;

    while (fast->next)

    {

        if (fast->next->next == NULL)//快指针一次前进两个节点

        {

            return NULL;

        }

        fast = fast->next->next;

        slow = slow->next;

        if (fast == slow)//不能比较val要比较地址,否则有相同的val会出错

        {

            //链表有环

            flag = 1;

            slow = head;//将慢指针置会链表头节点,快指针指向相遇点

            break;

        }

    }

    if (flag)//带环

    {

        while (1)

        {

            if (fast == slow)

            {

                break;

            }

            fast = fast->next;

            slow = slow->next;

        }

        return fast;

    }

    return NULL;

}

第二种办法,转变为双链表求交点

将相遇点的下一个节点作为第二个链表的头节点

将相遇点置为空

求原链表与第二个链表的交点即可如图

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值