一、问题分析
先给出题目链接141. 环形链表 - 力扣(LeetCode)。 给出思路快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,若链表带环则两指针一定会在环里相遇,即小学中学中的追及问题。
我们给出实现代码:
/**
* Definition for singly-linked list.
* 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;
}
二、提出新疑问
本题本身不难,但是对思路我们进行扩展提出两个问题:
(一)为什么快指针每次走两步,慢指针走一步可以判定链表带环?
这个问题不难想到答案,当指针进环时,每走一步相当于快指针从后面追慢指针一步,距离终将缩小为0,即追上。
(二)快指针一次走3步,走4步,...,走n步可以吗?
我们先判断快指针一次走3步是否可行:
先假设当slow进环时,fast在这个位置,fast追上slow的距离为L,fast和slow每走一步相当于L-2,当L为偶数时,则能追上;L为奇数时,fast第一次将超过slow一步而不与slow相遇,如果L+N为奇数那此时fast距离追上slow有L+N-1步(偶数),所以可以追上,若L+N为偶数,则永远不能追上。那么现在,似乎可以推导fast一次走4步也分情况可以实现,所以只有fast一次走两步才能成功。
事实真的如此吗?如果当L为奇数时L+N不可能为偶数,那一次走3步就是可以实现的了。
我们把数学公式写出来:
假设在slow进环时,fast已经在环里转了n圈,,根据上述等式容易发现问题,当N为奇数时,因为2S为偶数,所以L+N必定为奇数,所以一次走3步也是可行的。后续的推导思路与走3步一致。
三、新问题
我们根据之前的用数学等式推导的方法,解决新的问题142. 环形链表 II - 力扣(LeetCode)
返回入环的第一个结点,那我们要分析当fast与slow相遇时的位置,因为此时才能判定确实有环(slow走一步fast走两步):
相遇时slow走的路程为:S+N;fast走的路程为L+n*(L+N)+N。(因为当fast追上slow时,n不可能等于0),此时有:
所以相遇位置再走S就能到入环第一个结点(因为S = L+整数倍环长度),所以让一个指针从meet位置往后走,一个指针从head位置往后走,当两指针相遇时,就为入环第一个结点,代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(fast == slow)
{
struct ListNode* meet = slow;
while(meet != head)
{
meet = meet->next;
head = head->next;
}
return meet;
}
}
return NULL;
}