【LeetCode】经典的环形链表

作者:@阿亮joy.
专栏:《阿亮爱刷题》
座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述


目录

    • 环形链表
    • 环形链表的延伸问题
      • 问题
      • 结论
      • 证明
    • 环形链表II
      • 结论
      • 证明
    • 环形链表问题转化成链表相交问题
    • 总结


环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。


示例 1:

【LeetCode】经典的环形链表_第1张图片
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

【LeetCode】经典的环形链表_第2张图片
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

提示:

  • 链表中节点的数目范围是 [0, 104]
  • -105 <= Node.val <= 105
  • pos 为 -1 或者链表中的一个 有效索引 。

思路:定义两个指针,分别为slowfastslowfast指向链表的开始,slow一次走一步,而fast一次走两步。如果链表不带环,fast将会为NULL;而如果链表带环的话,fast就会在环内跟slow相遇。这就是高中物理的相遇问题。以至于fastslow为什么会在环内相遇,待会再给大家证明。
【LeetCode】经典的环形链表_第3张图片

/*
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(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)//相遇表示链表带环
        {
            return true;
        } 
    }
    return false;//循环结束代表链表不带环
}

【LeetCode】经典的环形链表_第4张图片

环形链表的延伸问题

问题

  • 如果链表带环的话,按照slow一次走一步,fast一次走两步的走法,slowfast一定会在环内相遇吗?会不会在环内错过,永远遇不上?如果一定会相遇,请证明!
  • 为什么要slow一次走一步,fast一次走两步呢?那如果slow一次走一步,fast一次走n(n >= 3)步,slowfast还会不会一定在环内相遇?请证明!

结论

  • 如果链表带环,按照slow一次走一步,fast一次走两步的走法,slowfast一定会在环内相遇,不会错过。
  • 如果按照slow一次走一步,fast一次走n(n >= 3)步的走法,slowfast不一定会在环内相遇。

证明

【LeetCode】经典的环形链表_第5张图片
【LeetCode】经典的环形链表_第6张图片
【LeetCode】经典的环形链表_第7张图片

环形链表II

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL 。
示例 1:

【LeetCode】经典的环形链表_第8张图片
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

在上面,已经给出了环形链表延伸问题的结论和证明,但是怎么求环的入口点呢?

结论

一个指针从相遇点开始走,另一个指针从头结点开始走,它们会在环的入口点相遇。

证明

【LeetCode】经典的环形链表_第9张图片

/*
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
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)
        {
            //相遇
            struct ListNode* meetNode = slow;
            while(meetNode != head)//公式证明
            {
                meetNode = meetNode->next;
                head = head->next;
            }
            return meetNode;//返回环的入口点
        }
    }
    //链表不带环
    return NULL;
}

【LeetCode】经典的环形链表_第10张图片

环形链表问题转化成链表相交问题

其实《环形链表II》还有第二种思路,就是将环形链表问题转换成链表相交问题。如果不了解链表相交问题的话,可以看博主的上一篇博客:链表OJ题,里面有对链表相交问题的详细分析。在这里,也给大家实现一下这种思路。
【LeetCode】经典的环形链表_第11张图片

/*
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
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)
        {
            //相遇
            struct ListNode* meetNode = slow;
            //链表1的长度从head记到meetNode
            //链表2的长度从meetNode记到meetNode
            struct ListNode* list1 = head;
            struct ListNode* list2 = meetNode->next;
            int len1 = 1;
            int len2 = 1;
            //求长度
            while(list1 != meetNode)
            {
                len1++;
                list1 = list1->next;
            }
            while(list2 != meetNode)
            {
                len2++;
                list2 = list2->next;
            }
            //假设长链表为链表1
            struct ListNode* longList = head;
            struct ListNode* shortList = meetNode->next;
            //调整长链表
            if(len1 < len2)
            {
                longList = meetNode->next;
                shortList = head;
            }
            //长度链表先走长度差步
            int gap = abs(len1 - len2);//gap为绝对值函数
            while(gap--)
            {
                longList = longList->next;
            }
            //找交点
            while(longList != shortList)//不相同就往后找
            {
                longList = longList->next;
                shortList = shortList->next;
            }
            return longList;//返回交点
        }
    }
    //链表不带环
    return NULL;
}

【LeetCode】经典的环形链表_第12张图片


总结

本篇博客讲解了链表中最经典的问题——环形链表问题,该问题在很多大厂的面试也会经常考。希望大家能够掌握掌握环形链表问题。如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家啦!❣️

你可能感兴趣的:(阿亮爱刷题,链表,leetcode,数据结构,算法,c语言)