环形链表详解

目录

什么是环形链表?

(一)检测一个链表是否有环

分析:

1、为什么循环的结束条件是fast或者fast->next指向NULL?

2、为什么要求快指针fast一次移动两个节点,慢指针slow一次移动一个节点?

快指针fast一次移动两个节点,慢指针slow一次移动一个节点,两者一定会相遇吗?

小结:

fast一次不能移动三个/四个节点吗?

1)fast一次移动三个节点的情况:

① N为2的倍数时,

② N不为2的倍数时(奇数)

小结:

2)fast一次移动四个节点的情况

① N为3的倍数时,

② N不为3的倍数时

小结:

(二)寻找环的入口点

分析:


什么是环形链表?

环形链表是一种特殊类型的链表数据结构,其最后一个节点的"下一个"指针指向链表中的某个节点,形成一个闭环。换句话说,链表的最后一个节点连接到了链表中的某个中间节点,而不是通常情况下连接到空指针(null)。如图:

由于链表最后一个节点的下一个指针没有指向NULL,而是指向前面的某一个节点,所以我们不能再用 “current->next==NULL” 作为判断条件来遍历链表(这样会造成死循环),通常使用快慢指针来控制条件,下面的两个题目都是要借助快慢指针来实现。

(一)检测一个链表是否有环

先来说一下结论,如何判断一个链表是否有环:

1、首先,让快慢指针fast、slow指向链表的头节点Head;

2、让快指针fast一次向后移动两个节点,慢指针一次向后移动一个节点;

3、判断fast和slow是否移动到同一个节点上,如果移动到同一个节点上,就返回true;如果还没有移动到同一个节点上,进行步骤二。

4、上几个步骤通过循环完成,循环的结束条件是fast或者fast->next指向NULL。

代码如下:

struct ListNode* fast, *slow;
    fast = slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
            return true;
    }
    return false;

分析:

1、为什么循环的结束条件是fast或者fast->next指向NULL?

  • 当一个链表中有环时,fast和fast->next永远不会指向NULL
  • 当一个链表中没有环时,fast一定会移动到链表的结尾;又因为fast一次移动两个节点,所以有两种情况:①fast移动两次后,刚好指向NULL,结束循环;②fast移动一次后就已经指向NULL,此时再进行移动,就会出现对NULL的解引用

2、为什么要求快指针fast一次移动两个节点,慢指针slow一次移动一个节点?

  • 快指针fast一次移动两个节点,慢指针slow一次移动一个节点,两者一定会相遇吗?

移动的过程中有一下三种状态:

1)快指针fast和慢指针slow都没有进入环的入口点

2)快指针fast进入环的入口点(或已在环内)、慢指针slow没有进入环的入口点

3)快指针fast和慢指针slow都在环内

 为了方便画图,下面将抽象出一个模型来说明:

如果快指针fast和慢指针slow相遇,一定是在环内相遇,所以我们主要分析一下第三种状态。

 此时fast和slow的距离设为N,当fast和slow每次移动时,两者的距离就会变成N-1(fast向前移动两个,slow向前移动一个,所以距离就近一个),移动一次两者的距离就-1,(N-1-1-1-1-1-1-1-1-……)直到N会被减小到零,两者就相遇了。

小结:

快指针fast一次移动两个节点,慢指针slow一次移动一个节点时,两者一定会相遇。

  • fast一次不能移动三个/四个节点吗?

可以用同样的分析方法,来分析一下fast一次移动三个/四个节点的情况。

1)fast一次移动三个节点的情况:

此时fast和slow的距离设为N,当fast和slow每次移动时,两者的距离就会变成N-2,所以N-2-2-2-2-……

不难发现,如果是每移动一次距离减2的话,可能会出现N不会减到0的情况,所以这里有以下两种情况:

① N为2的倍数时,

N可以被减到零,快慢指针会相遇;(在未减到0之前,fast一直在slow的后面,fast追slow)

② N不为2的倍数时(奇数)

N不会被减到0,N最后一次会被减到1,此时fast再向前移动2个节点、slow向前移动1个节点时,fast会领先slow一圈,这时fast会跑到slow的前面一个节点,两者的距离会变成C-1(设C为环的长度)(这里会由原先的fast追slow变为slow追fast);

然后会再进行距离减小:(C-1)-2-2-2-2-……   到这里又有两种情况,(C-1)是否是2的倍数,如果是fast就可以和slow相遇;如果(C-1)不是2的倍数,此后便会重复上述过程,也就是说fast不会相遇。

小结:

所以fast一次移动三个节点,fast和slow不一定会相遇,它有两个影响点:

  • 与环的长度

(C-1)是否是2的倍数

  • 环入口点之前节点的个数有关

N是否是2的倍数-->在slow还没有到达环入口点前,fast会在环内一直循环,环入口点之前节点的个数不同,当slow到达环入口点时,fast的与slow的相对位置会不同,这样N就不同

2)fast一次移动四个节点的情况

移动四个节点的分析方法与三个的相同,下面我就CTRL+C和CTRL+V以下:

此时fast和slow的距离设为N,当fast和slow每次移动时,两者的距离就会变成N-3,所以N-3-3-3--……

不难发现,如果是每移动一次距离减3的话,可能会出现N不会减到0的情况,所以这里有以下两种情况:

① N为3的倍数时,

N可以被减到零,快慢指针会相遇;(在未减到0之前,fast一直在slow的后面,fast追slow)

② N不为3的倍数时

N不会被减到0,

N最后一次会被减到1,此时fast再向前移动3个节点、slow向前移动1个节点时,fast会领先slow一圈,这时fast会跑到slow的前面两个节点,两者的距离会变成C-2(设C为环的长度)(这里会由原先的fast追slow变为slow追fast);

然后会再进行距离减小:(C-2)-2-2-2-2-……   到这里又有两种情况,(C-2)是否是2的倍数,如果是fast就可以和slow相遇;如果(C-2)不是2的倍数,此后便会重复上述过程,也就是说fast不会相遇。

N最后一次会被减到2,此时fast再向前移动3个节点、slow向前移动1个节点时,fast会领先slow一圈,这时fast会跑到slow的前面一个节点,两者的距离会变成C-1

然后会再进行距离减小:(C-1)-2-2-2-2-……   到这里又有两种情况,(C-1)是否是2的倍数,如果是fast就可以和slow相遇;如果(C-1)不是2的倍数,此后便会重复上述过程,也就是说fast不会相遇。

小结:

所以fast一次移动三个节点,fast和slow不一定会相遇,它有两个影响点:

  • 与环的长度

(C-1)是否是2的倍数

  • 环入口点之前节点的个数有关

N是否是2的倍数-->在slow还没有到达环入口点前,fast会在环内一直循环,环入口点之前节点的个数不同,当slow到达环入口点时,fast的与slow的相对位置会不同,这样N就不同

(二)寻找环的入口点

这里还是先说明一下结论:

1、用两个指针cur1、cur2分别指向链表的头节点和快慢指针相遇的节点;

2、同时移动两个指针

3、当两个指针指向同一个节点时,该节点就是环的入口点

代码如下:

struct ListNode *detectCycle(struct ListNode *head)
{
    struct ListNode* fast, *slow;
    fast = slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            struct ListNode* meet = slow;
            struct ListNode * cur = head;
            while(meet != cur)
            {
                meet=meet->next;
                cur = cur->next;
            }
            return meet;
        }
    }
    return NULL;
}

分析:

 (设环的长度为C)当慢指针slow到达相遇点时,slow走过的距离设为L+X,则快指针fast走过的距离为L+X+N*C (N是slow为到达环入口点时,fast所走过环的圈数, N大于等于1)

  • slow走过的距离为什么是 L+X, 而不是L+X+圈数(即slow不会走环的一圈+X)?

最坏的情况是,如图

 由小学数学可以知道,相同的起点,相同的时间,fast的速度是slow速度的二倍,所以fast走过的距离是slow的二倍,所以当slow回到起点时,fast刚好第二次回到起点,此时两者相遇,而slow也只是走了一圈。

由于走过前面 L 长度的节点后,fast和slow之间一定有距离,所以当slow到达环的入口点时,fast可能不在环的入口点,所以slow肯定不会走一圈的。

  • fast走过的距离为什么是 L+X+N*C,并且N大于等于1?

这里我解释一下 “N*C,并且N大于等于1”,由于slow一次移动一个节点,fast一次移动两个节点,相同的时间下,fast的速度大于slow的速度,两者走过的距离肯定不相同,所以第一圈内,slow和fast不会相遇,只有当fast超过slow一圈或几圈后,两者才能相遇。

又因为fast走过的距离是slow走过距离的二倍,所以有  L+X+N*C = 2*(L+X)

进一步化简得,N*C = L+X -->>  (n-1)*C + C = L + X

又因为fast再环上转了(n-1)圈,但是fast与slow的相对位置不变,所以得到下式:

C = L + X

L=  C - X = N (N是相遇点与环的入口点的位置,顺时针)

由此可以得到上述结论。

当然,寻找环的入口点还可以用另一种方法:

让快慢指针相遇点与下一个节点断开,然后将问题转化为两个链表的相交,环的入口点其实就是两个链表的相交点。

代码如下:

struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)
{
    if(headA==NULL && headB==NULL)
    {
        return NULL;
    }
    struct ListNode* curA = headA, * curB = headB;
    int count1 = 0, count2 = 0;
    while (curA->next != NULL)
    {
        count1++;
        curA = curA->next;
    }
    while (curB->next != NULL)
    {
        count2++;
        curB = curB->next;
    }
    int gap = abs(count1 - count2);
    struct ListNode* longlist = headA;
    struct ListNode* shortlist = headB;
    if (count1 < count2)
    {
        longlist = headB;
        shortlist = headA;
    }
    if (curB == curA)
    {
        while (gap--)
        {
            longlist = longlist->next;
        }
        while (longlist != shortlist)
        {
            longlist = longlist->next;
            shortlist = shortlist->next;
        }
        return longlist;
            
    }
    else
    {
        return NULL;
    }
}

struct ListNode* getmeetnode(struct ListNode* head)
{
    struct ListNode* fast, * slow;
    fast = slow = head;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)
        {
            return fast;
        }
    }
    return NULL;
}
struct ListNode* detectCycle(struct ListNode* head)
{
    // 找到相遇点
    
    struct ListNode* meetnode = getmeetnode(head);
    if(meetnode == NULL)
    {
        return NULL;
    }
    // 断开为两个链表
    struct ListNode* cur1, *cur2;
    cur1 = meetnode->next;
    meetnode->next=NULL;
    cur2 = head;

    // 找到两个链表的交点
    struct ListNode* IntersectionNode = getIntersectionNode(cur1, cur2);
    return IntersectionNode;

}


本次内容到此结束了!如果你觉得这篇博客对你有帮助的话 ,希望你能够给我点个赞,鼓励一下我。感谢感谢……

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值