原题如下——
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
思路————快慢指针 + 数量关系
题目的本质是
1.判断是否有环
2.若有环,找到入环节点
1.判断是否有环
判断环,这里用到了环的性质,即环没有终点。
若有一快一慢指针运动(暂不考虑离散性),则两指针必然相遇;若无环,则快指针将到达终点(尾节点或NULL)
其次要考虑快指针和慢指针的运动速度
不妨先设快指针一次移动2个节点,慢指针移动1个节点。
若有环,当快慢指针都进入环内,两者速度差为1,若慢指针入环时两者相距个节点,易知总共循环次,两者相遇
struct ListNode
{
int val;
struct ListNode *next;
};//链表基本特征
struct ListNode *detectCycle(struct ListNode *head)
{
if(!head || !head->next)
return NULL;
struct ListNode* slow = head, * fast = head, * begin = head;
slow = slow->next;
fast = fast->next->next;
while(fast != slow && fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
/*
if(fast != slow)
return NULL;
while(begin != slow)
{
begin = begin->next;
slow = slow->next;
}
return begin;
*/
}
问题来了,既然快指针速度为2、慢指针速度为1可以,那么快指针速度为3、4......能否判断是否有环?
再举快指针速度为3的例子:
假设有环,慢指针走到入环节点时:
(吐槽一下,win11的画图真心用不习惯~ 建议用win10的画图)
快指针走到了距离入环节点为C的位置,
快慢指针路程的数量关系如下(n为快指针此时走过的环数)——
解得参数的数量关系为
此时快慢指针距离为
在追击中,两者距离为
......
这里要进行分类讨论:
第一种:为偶数,两者距离逐渐变为
6
4
2
0
当两者距离为0时,两者相遇;
第二种:为奇数时,两者距离逐渐变为
7
5
3
1
此时慢指针领先快指针1个节点,下一个小循环,快指针向前走3个节点,慢指针走1个节点,快指针、慢指针发生交错不相遇,并领先1个节点,两者进入下一个追击——
此时
这里再次分类讨论:
为奇数时,为偶数,变为第一种情况,两者可相遇;
为偶数时,为奇数,变为第二种情况,两者再次形成的追击,不能相遇
所以这种情况下若有环,为奇数则两者必相遇,不为奇数为偶数则相遇,为奇数为偶数则永远不相遇,那么为偶数、为奇数能否成立?
上面我们已经得出 ,当为偶数时,根据运算性质可知必然为偶数,也就是为偶数、为奇数无法成立,根本没有这种情况
综上,快指针速度为3个节点,慢指针为1个节点,依旧可以判断链表是否含有环。
一般情形下,当快指针速度为,慢指针速度为,仍可按上述思想处理。
2.寻找入环节点
由上述知,若链表存在环 则快慢指针相遇. 就快指针速度为2个节点,慢指针速度为1个节点来讨论
如图,两指针在环内相遇时,两者顺时针正向方向距离入环节点为,根据路程的数量关系有
(N为慢指针走过的环数,M为快指针走过的环数)
在这里有隐藏的数量特征,即
直觉想象一下,当在环内快慢指针开始追击时,快指针会在一圈内追上慢指针,
当然也可以使用假设证明:
假设 ,慢指针刚入环时快慢指针距离
易知经历次过程后快指针追上慢指针,此时慢指针在环内走过的路程为,未走完一环,即证明完毕
化简上述3式得
得到
若想要找到入环节点,只需在链表起始位置设置一指针begin,使其速度与slow指针相同,
当指针begin经过路程,指针slow遍历次环并与begin相遇,此时记为入环节点
struct ListNode
{
int val;
struct ListNode *next;
};//链表基本特征
struct ListNode *detectCycle(struct ListNode *head)
{
//if(!head || !head->next)
// return NULL;
struct ListNode* slow = head, * fast = head, * begin = head;
/*
slow = slow->next;
fast = fast->next->next;
while(fast != slow && fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
if(fast != slow)
return NULL;
*/
while(begin != slow)
{
begin = begin->next;
slow = slow->next;
}
return begin;
}
完整代码如下
struct ListNode
{
int val;
struct ListNode *next;
};//链表基本特征
struct ListNode *detectCycle(struct ListNode *head)
{
if(!head || !head->next)
return NULL;
struct ListNode* slow = head, * fast = head, * begin = head;
slow = slow->next;
fast = fast->next->next;
while(fast != slow && fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
if(fast != slow)
return NULL;
while(begin != slow)
{
begin = begin->next;
slow = slow->next;
}
return begin;
}