链表的带环问题---数据结构

前言:

在我们日常所见到的链表当中,极大多数是下面的这种结构:

通过遍历上述链表可以找到链表的尾部,而有一种链表结构它没有尾部,这种链表就叫做环形链表,示意图如下: 

上述的链表没有尾部,相对于我们常见的链表而言,他的尾节点的 next 指针指向链表中的任意一个节点,也就在链表中形成了环形结构,下面我们来看一下如何判断链表是否存在带环结构。 

分析:

带环链表的示意图:

 解决思路一(相对速度为 1 时):

定义两个快(fast)、慢(slow)指针同时指向链表的头结点,快指针每次走两步(fast = fast->next->next),慢指针每次走一步(slow = slow->next),如果 快指针 在走的过程中能够追上 慢指针 ,那么此链表就是带环链表,下面是代码实现:

//链表中存在环则返回 true ,否则返回 false

bool hasCycle(struct ListNode *head) {
    struct ListNode * fast = head;
    struct ListNode * slow = head;
    while(fast && fast->next)     //当fast 或者 fast的next指针为空时说明链表中不存在环
    {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        {
           return true;
        }
    }
    return false;  
}

 那么这里大家一定会有疑惑,为什么 fast 指针一定会追上 slow 指针呢,这其实就是一个追击问题,下面我们画图来解决:

1-> 最开始的时候,slow 与 fast 都指向链表的头结点,并向后移动(fast每次走两步,slow每次走一步)


2-> 当 slow 走到环的入口时,此时 fast 已经在环内了(可能绕环走了多圈,也可能还未走到一圈),我们设他们之间的距离为 N ,因为他们的相对速度为 1 (fast的速度减去slow的速度),所以之后每走一次,他们之间的距离都会减小 1 ,直到他们同时走了 N 次,fast就追上了slow。

  注:在追击的过程中,slow 有没有可能绕环走了一圈,fast 还没有追上呢?答案时不可能的,因为当他们共同走了N次后 ,fast 追上 slow ,而 slow 一次走一步,所以此时 slow 走的距离也就是N,而 N < C (C:环的周长),故此 slow 不会走到一圈,而是在 slow 走第一圈的时候 fast 就会将其追上。

解决思路二(相对速度为 2 时):

 当然看到这里,大家肯定有一个疑惑,难道fast 一定要一次走2步吗?不可以一次走3、4、5、......步吗?这里我以 fast 一次走3步,slow 一次走1步为例,来给大家讲解,其他的情况也可以类似分析。

1-> 最开始 fast 与 slow 都指向链表的第一个节点,fast 以一次走 3 步、slow 以一次走 1 步的速度向后走。

2->  当 slow 走到环的入口的时候,fast 已经在环内了(fast 可能在环内走了多圈),此时他们之间的距离为 N ,之后 slow 就进入环内 ,fast 开始追击 slow ,相对速度为2。

 

     1、当他们之间的距离 N 为偶数时,因为相对速度为2 ,故他们之间的距离变化为 :N 、N-2、N-4、........ 、2、0。所以 fast 在第一次追击过程中就能够追上 slow 。

     2、当他们之间的距离 N 为奇数时,因为相对速度为2 ,故他们之间的距离变化为 :N 、N-2、N-4、........ 、3、1、-1。-1代表的就是:当他们之间的距离变成1时,fast 、slow 在同时走一次,fast 将领先 slow 1步,他们之间的距离将变为了 C-1,并且开始新的一轮追击。

        (1)、当C为奇数时, C-1 为偶数,在新的一轮追击过程中,他们之间的距离变化为:C-1、C-3、......、4、2、0。fast 将在这一轮能够追上 slow。

        (2)、当C为偶数时, C-1 为奇数,他们之间的距离变化为:C-1、C-3、......、3、 1、-1。这表明他们在这一轮的追击过程中,fast 依旧追不上 slow ,他们之间的距离将再次变为C-1,所以 fast 将永远追不上 slow。

但事实并非与我们上述的分析一样,当 N 为奇数,并且 C 为偶数时的这种情况是不存在的,下面我们来进一步分析 N 与 C 之间的关系。

 

我们将链表的起始节点与环的入口之间的距离设为 L ,设当 slow 走到环的入口时, fast 已经在环内走了 X 圈,故:

    slow 走的距离 S1 = L ;

    fast   走的距离 S2 = L + X*C + C-N ; ②

    并且                  S2 = 3S1;

将上面的①②③式子联立可得到:

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

故此我们可以得到等式左边的 (X+1)*C - N 一定为偶数:

  ①、当C为偶数时,(X+1)*C 为偶数,要想(X+1)*C - N 为偶数 ,N 必须为偶数

  ②、当C为奇数时,(X+1)*C 为奇数,要想(X+1)*C - N 为偶数 ,N 必须为奇数

  故此,C与N的奇偶性必须相同,所以在上诉我们得出的结论当中,N 为奇数C为偶数的情况不    存在,所以 fast 一次走3步,slow 一次走1步的解决思路可行,fast 可以追上 slow。

以上也就解决了我们的链表带环问题,如果还想进一步练习,可以刷一下下面的题目。

141. 环形链表 - 力扣(LeetCode)

参考代码:

bool hasCycle(struct ListNode *head) {
    struct ListNode * fast = head;
    struct ListNode * slow = head;
    while(fast && fast->next)  //当fast不能再一次走两步就跳出循环
    {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        {
           return true;
        }
    }
    return false;  
}

142. 环形链表 II - 力扣(LeetCode)

参考代码:

//这里可以画图理解
//设从链表第一个节点到环的入口点之间的长度为L,环的长度为C,
//先让 fast 走C步,再让fast与slow同时走 L 步,相遇的点即为环的入口点

struct ListNode* detectCycle(struct ListNode* head) {
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast)
        {
            break;
        }
    }
    if (fast == NULL || (fast->next) == NULL) //链表不存在环
    {
        return NULL;
    }
    int cnt = 1;  //统计环的长度
    fast = fast->next; 
    while (slow != fast)  //统计环的长度
    {
        cnt++;
        fast = fast->next;
    }
    fast = slow = head;  //让fast 与 slow 重新指向链表的第一个节点
    while (cnt--)        //让fast 先走环的长度
    {
        fast = fast->next;
    }                     
    while (fast != slow)  //再让fast 与 slow 同时走,他们相遇的点即为环的入口点
    {                    
        fast = fast->next;
        slow = slow->next;
    }
    return fast;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值