作者:@阿亮joy.
专栏:《阿亮爱刷题》
座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
给你一个链表的头节点
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
不一定会在环内相遇。
证明
给定一个链表的头节点 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;
}
本篇博客讲解了链表中最经典的问题——环形链表问题,该问题在很多大厂的面试也会经常考。希望大家能够掌握掌握环形链表问题。如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家啦!❣️