【LeetCode】经典的环形链表

简介: 【LeetCode】经典的环形链表

👉环形链表👈


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

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



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



示例 1:

7085142792d5428e8787f3002631f0b0.png

输入:head = [3,2,0,-4], pos = 1

输出:true

解释:链表中有一个环,其尾部连接到第二个节点。



示例 2:


7a3fbc4a484e4b61965fa1b63cb3be8a.png

输入:head = [1], pos = -1

输出:false

解释:链表中没有环。



提示:


链表中节点的数目范围是 [0, 104]

-105 <= Node.val <= 105

pos 为 -1 或者链表中的一个 有效索引 。


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


1e3d854528a24c0c8e7dd0a07f5c691e.png

/*
 * 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;//循环结束代表链表不带环
}

9171bd0a2a914e778ff37f85afcca0c9.png


👉环形链表的延伸问题👈

问题

如果链表带环的话,按照slow一次走一步,fast一次走两步的走法,slow和fast一定会在环内相遇吗?会不会在环内错过,永远遇不上?如果一定会相遇,请证明!

为什么要slow一次走一步,fast一次走两步呢?那如果slow一次走一步,fast一次走n(n >= 3)步,slow和fast还会不会一定在环内相遇?请证明!


结论


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

证明

693581205b624b36b946be133f44032b.png

904c5ad4652f4286bd9ed897b5a0b057.png


2cdd4af7d2ab44e29d90d49ab2ebd174.png


👉环形链表II👈


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

示例 1:

7085142792d5428e8787f3002631f0b0.png

输入:head = [3,2,0,-4], pos = 1

输出:true

解释:链表中有一个环,其尾部连接到第二个节点。


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


结论


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


证明

7f93f7890ce74bbbb297d5621f9e9c14.png

/*
 * 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;
}

933bc036b9774891bae40b72a0534d55.png


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


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

bfa36aa75735442286cb87175b26d844.png

/*
 * 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;
}

b1baa4cc89a843f2919875bbf413ddc2.png


👉总结👈


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



相关文章
|
11月前
【力扣】-- 移除链表元素
【力扣】-- 移除链表元素
123 1
|
11月前
Leetcode第21题(合并两个有序链表)
这篇文章介绍了如何使用非递归和递归方法解决LeetCode第21题,即合并两个有序链表的问题。
148 0
Leetcode第21题(合并两个有序链表)
|
5月前
|
算法 Go
【LeetCode 热题100】23:合并 K 个升序链表(详细解析)(Go语言版)
本文详细解析了 LeetCode 热题 23——合并 K 个升序链表的两种解法:优先队列(最小堆)和分治合并。题目要求将多个已排序链表合并为一个升序链表。最小堆方法通过维护节点优先级快速选择最小值,;分治合并则采用归并思想两两合并链表。文章提供了 Go 语言实现代码,并对比分析两种方法的适用场景,帮助读者深入理解链表操作与算法设计。
180 10
|
11月前
LeetCode第二十四题(两两交换链表中的节点)
这篇文章介绍了LeetCode第24题的解法,即如何通过使用三个指针(preNode, curNode, curNextNode)来两两交换链表中的节点,并提供了详细的代码实现。
121 0
LeetCode第二十四题(两两交换链表中的节点)
|
11月前
Leetcode第十九题(删除链表的倒数第N个节点)
LeetCode第19题要求删除链表的倒数第N个节点,可以通过快慢指针法在一次遍历中实现。
120 0
Leetcode第十九题(删除链表的倒数第N个节点)
|
11月前
|
索引
力扣(LeetCode)数据结构练习题(3)------链表
力扣(LeetCode)数据结构练习题(3)------链表
231 0
|
11月前
【LeetCode 10】142. 环形链表 II
【LeetCode 10】142. 环形链表 II
83 0
|
11月前
【LeetCode 09】19 删除链表的倒数第 N 个结点
【LeetCode 09】19 删除链表的倒数第 N 个结点
149 0
|
11月前
【LeetCode 08】206 反转链表
【LeetCode 08】206 反转链表
61 0
|
11月前
【LeetCode 06】203.移除链表元素
【LeetCode 06】203.移除链表元素
83 0