【算法专题--链表】环形链表--高频面试题(图文详解,小白一看就会!!)

目录

一、前言

 二、什么是环形链表?

🥝 环形链表的概念详解

🍇 环形链表的特点 

 🍍环形链表的延申问题

 三、高频面试题

 ✨环形链表

 ✨环形链表II

 ✨环形链表的环长

 四、总结

五、共勉 


一、前言

        环形链表,可以说是--链表专题--,最经典的一种结构,也是在面试中频率最高的一道题目,通常在面试中,面试官可能会根据环形链表的结构延申出很多问题,所以大家需要对这环形链表结构非常熟悉哦!!
      本片博客就来详细的讲讲解一下
环形链表,让我们的面试变的更加顺利!!!

 二、什么是环形链表?

🥝 环形链表的概念详解

       环形链表是一种特殊类型的链表数据结构,其最后一个节点的"下一个"指针指向链表中的某个节点,形成一个闭环。 换句话说,链表的最后一个节点连接到了链表中的某个中间节点,而不是通常情况下连接到空指针 (null) 
       如果大家对链表还不熟悉可以先看看这篇文章:单链表详解

🍇 环形链表的特点 

 特点:
1️⃣:若一个链表带环,那么用指针一直顺着链表遍历,最终会回到某个地方。
2️⃣:  我们可以定义两个指针(快慢指针),两个指针均从表头开始同时遍历链表,快指针一次走两步,慢指针一次走一步。如果链表带环,那么快慢指针一定会相遇,否则快指针会先遍历完链表(遇到NULL)。

  • 若是你不明白为什么链表带环,快慢指针就一定会相遇,那么你可以想想龟兔赛跑: 

 🍍环形链表的延申问题

 1️⃣:为什么慢指针走一步快指针走两步,他们一定会在环里面meet吗?会不会永远追不上?

  •   首先 慢指针快指针一定是快指针先进环
  •  随着慢指针进环快指针已经在环了走了一段距离,走了多少和环的大小有关
  •  假设慢指针进环的时候距离快指针距离为 N快指针开始追慢指针
  •  在追击过程中,快指针往前走两步慢指针往前走一步每走一次它们之间的距离就缩小 1 
  •  它们之间的距离变化为 N ,N-1 ,N-2 , . . . 1 ,0  ,它们之间的距离为 零 就会相遇所以一定追得上

 2️⃣: 为什么一定是 慢指针走一步快指针走两步?快指针能走其它步数吗?

  •  假设慢指针进环的时候,快指针慢指针之间的距离为 N
  •  在追击的时候,快指针走三步慢指针走一步,每走一次,它们之间的距离缩小 2

 此时分两种情况:

  • N 偶数,则它们之间的距离会减少到 0 相遇
  • N奇数,则它们之间的距离会减少到1 然后减少到 -1减少到 -1 也就意味着它们之间的距离又变为 C - 1 (C是环的周长)

此时又分两种情况:

  •  若 C奇数 ,则 C - 1 为偶数 ,因为它们之间的距离一次缩短 2 所以它们会相遇
  •  若 C 偶数 ,则 C - 1 为奇数 ,也就是说它们之间的距离还是奇数,它们永远不会相遇

 由此可以得出结论:快指针一次不走两步,存在追不上的情况。
 所以 必须 ---- 每次慢指针走一步 , 快指针走两步


3️⃣ :如何才能得知 环口 的节点是哪一个呢?

  •  追上的相遇的过程中,慢指针的距离:L+X快指针的距离:L+NC+X
  • 由于不知道 快指针 在环里多跑了几圈,所以设了一个N,但是N肯定>=1, 
  • 因为快指针是满指针的两倍,所以2(L+X)=L+NC+X。整理可得   L= NC - X

     至此,我们发现链表头到环入口的距离 相遇点到环入口的距离 C-X 相差 N 个环的长度。
 
  结论:一个指针指向链表头,一个指针指向第一次相遇点 , 并使两个指针的速度都为 1 ,一直前进直到它们相遇,第二次相遇的位置一定为环的入口位置。

 三、高频面试题

 ✨环形链表

题目链接:141. 环形链表 - 力扣(LeetCode) 

解题思路: 

  • 我们可以使用快慢指针法。定义步长为2的快指针步长为1的慢指针遍历链表。我们假设链表如果存在环,则快慢指针在某一时刻一定会在环内相遇;而如果不存在环,由于快指针速度比慢指针快,因此它们永远无法相遇。

 代码:

class Solution {
public:
    // 题目已经给出了 单链表的 节点构造

    bool hasCycle(ListNode *head) 
    {
        // 定义两个快慢指针
        ListNode* slow = head;    //  每次走一步
        ListNode* fast = head;    //  每次走两步

        // 当链表 有环存在时 fast 和 fast-next 一定不会指向空
        // 当快指针不为空且其下一个节点也不为空时,继续循环
        while(fast!=nullptr && fast->next!=nullptr)
        {
            slow = slow->next;
            fast = fast->next->next;
            // 相交成环
            if(slow == fast)
            {
                return true;
            }
        }
        return false;
    }
};

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

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

 ✨环形链表II

 题目链接:142. 环形链表 II - 力扣(LeetCode)

解题思路: 

  • 起初快指针速度为2,慢指针速度为1,如果快慢指针没有相遇,则返回NULL代表不存在环;如果相遇了,我们就让 其中一个指针重新指向链表头,并使 两个指针的速度都为1,一直前进直到它们相遇,相遇的位置一定为环的位置。这是因为从 相遇点走 L 步后的位置与走C - X 步后的位置一致。

代码: 

class Solution {
public:
    ListNode *detectCycle(ListNode *head) 
    {
        // 初始化两个指针,快指针fast和慢指针slow,都指向链表头节点
        ListNode* slow = head;
        ListNode* fast = head;


        // 当快指针和其下一位都不为空时(即未到达链表尾部)
        while(fast&&fast->next)
        {
             // 慢指针每次前进一步
             slow = slow->next;
             // 快指针每次前进两步
            fast = fast->next->next;

            // 如果两个指针第一次相遇
            while(slow==fast)
            {
                // 将相遇的节点赋值给 meet变量
                ListNode* meet = fast;

                // 从头节点开始,另一个指针从相遇点开始,两者每次各前进一步
                // 直到两个指针再次相遇,相遇点即为环的起始节点
                while(head!=meet)
                {
                    head = head->next;
                    meet = meet->next;
                }

                // 返回找到环的入口节点
                return meet;
            }
        }

        return NULL;
    }
};

✨环形链表的环长

题目描述: 
给定一个链表的头节点 head ,返回链表环中环的长度。 如果链表无环,则返回 0。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。

解题思路: 

  • 我们同样可以先使用快慢指针判断链表是否存在环,当二者第一次相遇时说明链表存在环。而当快慢指针相遇后我们再次让快慢指针进行追逐战,此时它们之间的距离为环长 C,由于每次追逐它们的距离都会缩短1,因此我们可以定义一个计数器count记录第二次相遇时所追逐的次数即为链表的环长。具体过程如下:

代码: 

 struct ListNode {
    int val;
    struct ListNode* next;
    
};
int CycleLength(struct ListNode* head)
{
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    while (fast && fast->next) //当到达或即将到达链表尾时退出循环
    {
        //快慢指针移动
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow) //第一次相遇,存在环
        {
            //统计第二次相遇所追逐的次数
            int count = 0;
            do
            {
                fast = fast->next->next;
                slow = slow->next;
                count++;
            } while (fast != slow);
            //相遇了,返回追逐次数即为环长
            return count;
        }
    }
    //不存在环,返回0
    return 0;
 
}

 四、总结

         以上就是今天要讲的内容,做题的时候,就是奔着双指针的思路进行解决的,练习这么多次现在逐渐找到了点感觉。我感觉就是双指针的关键点就是在于该怎么去移动两个指针,理清这一点之后离结果也就不远了,判断链表是否有环应该是老生常谈的一个话题了,最简单的一种方式就是快慢指针。慢指针针每次走一步,快指针每次走两步,如果相遇就说明有环,如果有一个为空说明没有环。所以就赶紧记录一下这时刻。

五、共勉 

     以下就是我对 环形链表 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对 链表专题 的理解,请持续关注我哦!!! 

最近更新

  1. TCP协议是安全的吗?

    2024-06-10 11:36:03       14 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-10 11:36:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-10 11:36:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-10 11:36:03       18 阅读

热门阅读

  1. GoogLeNet

    GoogLeNet

    2024-06-10 11:36:03      8 阅读
  2. MySQL和Oracle区别

    2024-06-10 11:36:03       7 阅读
  3. LeetCode 239. 滑动窗口最大值

    2024-06-10 11:36:03       9 阅读
  4. B树、B+树与索引、联合索引

    2024-06-10 11:36:03       9 阅读
  5. 家族企业如何找到合适的人才

    2024-06-10 11:36:03       10 阅读
  6. 捡贝壳问题

    2024-06-10 11:36:03       8 阅读
  7. C#.net MassTransit和DotNetCore.CAP区别

    2024-06-10 11:36:03       11 阅读
  8. 动态规划路径问题(C++)

    2024-06-10 11:36:03       10 阅读
  9. Spring (48)Feign

    2024-06-10 11:36:03       7 阅读
  10. Git常用命令

    2024-06-10 11:36:03       7 阅读
  11. python-win10跑通chattts笔记(亲测可跑)0.8.010

    2024-06-10 11:36:03       7 阅读