本题本质上使用 快慢指针 解题。
解题思路
- slow和fast开始都指向链表的开始。
- slow一次走一步,fast一次走两步。如果不带环,fast或者fast的下一个结点指向空。
不是环形链表的两种情况
问题抛出
由此,可以抛出两个问题。
- slow一次走一步,fast一次走两步,存在环形链表的情况下,为什么slow和fast一定会在环中相遇,会不会在环里错过,永远追不上?请证明。
- 为什么slow走一步,fast走两步,能不能fast一次走3、4、5……步呢?请证明。
分析证明
首先对以上两个问题得出结论
- 一定会相遇
- fast一次走n步,n>2不一定会相遇
在带环情况下:
第一步:slow和fast都在走,fast一定先入环,这时slow走了入环前的一半路程。
第二步:随着slow进环,fast已经在环里面走了一段,走了多少圈和环大小有关。
假设slow进环的时候,slow和fast距离是n,fast此时开始追逐slow。
fast每次走两步的情况
此时slow一次走一步,fast往前走两步。每追一次,判断是否相遇。
每追一次,fast和slow的距离变化:
N-1 -> N-2 -> N-3 -> … -> 0(相遇)
每追一次,他们之间距离减少1,当他们之间的距离减少到0的时候,即为相遇点。
fast每次走三步的情况
每追一次,fast和slow的距离变化:
N -> N-2 -> N-4 -> n-6
当N是偶数时,最后距离会减少到0,两者相遇。
当N是奇数时,最后距离会变成 -1,也就是 slow和fast之间的距离变为C-1(C是环的长度)。
如果C-1是奇数,永远追不上。
如果C-1是偶数,则在第二圈追上。
fast每次走四步的情况
每追一次,fast和slow的距离变化:
N -> N-3 -> n-6 -> n-9 -> …
如果N是3的倍数,最后两者之间的间隔变为0,可以追上。
反之,不是3的倍数。
- 2 -> -1 (即C-1)意味着两者之间的距离是C-1
- 1 -> -2 (即C-2)意味着两者之间的距离是C-2
同理:如果C-1和C-2是3的倍数,则在第二圈可以追上。否则追不上,依次类推。
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
// 快慢指针, 快指针一次走两步,慢指针一次走一步
struct ListNode* fast, *slow;
fast = slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
return true;
}
return false;
}
代码其实很简单,重要的是推导的过程。