链表带环问题

        在链表的学习中,我们有时候会遇到环形链表,那么环形链表是什么呢?下面由我为大家讲解!

一,什么是环形链表

哈哈哈~~其实也就是带环的链表啦~~

如图所示

又或者是这样

所以带环链表有一个特性,进入就会出不来,进入了死循环~~!


二.关于环形链表的经典问题

1.下面有一道例题

这一题的最好想象的思路便是快慢指针,那么什么是快慢指针呢?

(1)快慢指针

首先,对链表的操作基本都是靠指针去完成的,因此肯定需要一个指针去维护这个链表,但环形链表有一个很重要的特性:就是由于有环状结构,因此指针一旦进入环状,就会在一直在链表里面,永远走不出来。因此我们应该用两个指针,一个遍历速度快一点,另一个遍历速度慢一点,简称“快慢指针”。

(2)思路

引用快慢指针之后,一旦慢指针进入环,通过快慢指针的速度差,当快指针追上慢指针的时候,就代表链表是环状的,否则快指针一旦为空,就说明链表不是环状链表。

即快慢指针相遇的时候,就可以表示它们带环!

 我们这边先假设快指针比慢指针多走1步,即快指针走两步,慢指针走一步~

我们这样就容易得出代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(struct ListNode *head) 
{
    struct ListNode * slow=head,*fast=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(slow==fast)
        return true;
    }
    return false;
    
}

三.其中存在的问题

这样就可以得出代码~~~

但是爱思考的人都会想

1.   为什么一定会相遇?有没有可能错过或者永远的追不上?

2.   满指针走1步,快指针走3步,4步,5步或者n步是否能解决问题?

  • 下面我们先解决第一个问题~

首先我们假设slow(慢指针)进入环的时候与fast(快指针)的距离为n

这样就解决了第一个问题~!

  • 再来第二个问题

首先fast先走3步

我们还是假设slow进入fast时距离为N(下面都是如此)

这时slow和fast的历程有着两种情况(N为奇偶,左边为偶数,右边为奇数)

这时出现追成-1的概念,那么是什么问题呢?

即错过了,进行新的一轮追击

  1. 若C-1为偶数------这样就直接追上了
  2. 若C-1为奇数------那么好像就永远追不上

问题是我们真的永远追不上嘛?

四:分析

我们先总结一下

永远追不上的条件是什么    -----》同时存在N为奇数C为偶数的时候,便永远追不上~~

我们想一下是否会真的存在这种情况,这时就需要我们的一些数学知识

  • 首先我们先假设slow进环前走的距离为L,且slow进环时fast已经走了x圈
  • 这时slow走的距离为L,fast走的距离为L + x*C + C-N
  • fast的速度时slow的三倍

这时我们可以得出两个等式

3*L =L+x*C+C-N                 2*L=(x+1)*C-N

偶数=(x+1)*偶数-奇数,我们会发现这个表达式并不会存在,偶数减去奇数必然不会为偶数

只有奇数减奇数才能等于偶数--》(x+1)*偶数一定为偶数

反证N为奇数且C为偶数的情况不能同时存在,永远追不上的条件不存在(所以我们还是可以追上滴) 

五.结论

N若为偶数第一轮就追上了。

N若为奇数第一轮追不上,C-1是偶数时第二轮就追上了~


ps.上面只说了走3步的情况,还有走4步,5步或n步的情况都与三步相似都是可以追上的~只不过是多走的趟数!


六. 环形链表另一个问题

1.如何找到环形链表的入口节点

(1) 问题描述

简单来说就是这里

(2)思路

这里我们通过环形链表可以得出一些结论:

这里我们设起点到入口的距离是 L;入口到相遇点的距离为 X;链表的环型部分为 C;快指针速度为 2;慢指针速度为 1.

因此慢指针走的路程为:L+X。 

到了这里,你或许就会问,为什么慢指针一定会在一圈内被快指针追上呢?

假设最坏的情况(即快慢指针最远的相对距离)为在慢指针刚到入口时快指针在它的前面,那么距离为 C - 1。因为这两个指针的相对速度为1,因此总共要移动 (C - 1)/ 1 次,因此慢指针的移动距离为:1 *((C - 1)/ 1)< C。

言归正传,快指针走的路程为:n*C+X+L

因此便有了如下等式:2(L + X)= n * C + X + L;

化简后就是这个结论:L=n*C-X.

(3)  L = n * C - X

这个式子是什么意思呢?其实就是说在相同的时间内,慢指针从起点走到入口时,快指针从相遇点(X处)开始走 n 圈,最后与慢指针相遇,且此时新的相遇点恰好是环形链表的入口处!!!

(4)代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* detectCycle(struct ListNode* head) {
    struct ListNode *slow = head, *fast = head;
    while (fast != NULL) {
        slow = slow->next;
        if (fast->next == NULL) {
            return NULL;
        }
        fast = fast->next->next;
        if (fast == slow) {
            struct ListNode* ptr = head;
            while (ptr != slow) {
                ptr = ptr->next;
                slow = slow->next;
            }
            return ptr;
        }
    }
    return NULL;
}