👉环形链表👈
给你一个链表的头节点
head
,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入: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为什么会在环内相遇,待会再给大家证明。
/* * 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;//循环结束代表链表不带环 }
👉环形链表的延伸问题👈
问题
如果链表带环的话,按照slow一次走一步,fast一次走两步的走法,slow和fast一定会在环内相遇吗?会不会在环内错过,永远遇不上?如果一定会相遇,请证明!
为什么要slow一次走一步,fast一次走两步呢?那如果slow一次走一步,fast一次走n(n >= 3)步,slow和fast还会不会一定在环内相遇?请证明!
结论
- 如果链表带环,按照
slow
一次走一步,fast
一次走两步的走法,slow
和fast
一定会在环内相遇,不会错过。- 如果按照
slow
一次走一步,fast
一次走n(n >= 3)
步的走法,slow
和fast
不一定会在环内相遇。
证明
👉环形链表II👈
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL 。
示例 1:
输入:
head = [3,2,0,-4], pos = 1
输出:
true
解释:链表中有一个环,其尾部连接到第二个节点。
在上面,已经给出了环形链表延伸问题的结论和证明,但是怎么求环的入口点呢?
结论
一个指针从相遇点开始走,另一个指针从头结点开始走,它们会在环的入口点相遇。
证明
/* * 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; }
👉环形链表问题转化成链表相交问题👈
其实《环形链表II》还有第二种思路,就是将环形链表问题转换成链表相交问题。如果不了解链表相交问题的话,可以看博主的上一篇博客:链表OJ题,里面有对链表相交问题的详细分析。在这里,也给大家实现一下这种思路。
/* * 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; }
👉总结👈
本篇博客讲解了链表中最经典的问题——环形链表问题,该问题在很多大厂的面试也会经常考。希望大家能够掌握掌握环形链表问题。如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家啦!💖💝