一.解题
力扣经典链表判环问题:141. 环形链表
而解决这样的环形链表,快慢指针是经典算法
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {
ListNode * slow = head, *fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
return true;
}
}
return false;
}
二.疑问与解析
疑问.
在这个算法中我们采取了快慢指针步长差为 1,那为什么要一定是 1 呢?为什么1 就一定能追的上呢?能不能是 2 呢?3 呢?N 呢?如果不是 1,还一定追的上吗?
解析.
让我们先来解析一下,为什么 1 就一定能追的上呢?
Slow 走 1,Fast 走 2:
-
相对速度是 1。
-
这就好比你在操场跑圈,前面的人比你快 1m/s。不管你们其实距离多少(N),每一秒距离缩短 1 米:N, N-1, N-2 ... 0。
-
结论:因为距离是一步步减小的,一定 会减到 0(相遇)。
所以,目前来看 1 是最优的,不需要担心什么,而快慢指针的算法步长差普遍也为 1.
如果 Slow 走 1,Fast 走 3 呢?
-
相对速度:3 - 1 = 2
-
距离变化:每次缩短 2 米
-
风险:如果初始距离 N 是奇数,距离变化会是 N N-2... 1 -1
-
这个 -1 意味着 Fast “跳过” 了 Slow,跑到了 Slow 前面 1 个单位的位置
-
此时,新一轮的追击距离变成了 C-1 (C 是环的周长),那如果 C-1 也是奇数呢?也就是 C 是偶数,也就意味着永远追不上了。
-
好像我们得出来了一个结论,也就是当 N 为奇数,且 C为偶数的时候,就永远追不上了。
但事实真就如此吗?N 是否与 C 有某种制约关系呢?这样的情况会不会永远不会出现?
我们注意到,slow 的速度是 fast 的一半,而他们是同时出发的,根据 S = v * t,那他们所走过的路程也有关系,是否能找到N 和 C 的关系呢?
代数证明:
-
假设:Slow 进环时,走了L的路程。
-
此时:Fast 走了 3L 的路程(因为速度是 3 倍)。
-
几何关系:Fast 此时在环里,位置是 L + x *C + N(x 是圈数,N 是入环后的相对距离)。
-
建立等式:
3L = L + x*C + N
2L = x*C + N
-
奇偶性分析(高能预警):
-
等式左边 2L:一定是偶数。
-
等式右边 x*C + N:必须也是偶数。
-
如果 C 是偶数(死锁条件之一),那么 x*C 也是偶数。
-
那么 N 必须是:偶数 - 偶数 = 偶数。
-
结论:
-
如果要死循环,必须满足C是偶数 且 N 是奇数。
-
但是数学公式告诉我们:只要 C 是偶数,N 就一定是偶数!
-
结论: “N 是奇数”和“C 是偶数”这两个条件,永远不可能同时成立。
终极结论:
即使是用 Fast 走 3 步,它们也一定会相遇! 根本不存在追不上的情况。
但在这类问题,我们还是更倾向于用步长差为 1 来解决
原因如下
-
代码代价:
-
走 2 步:
fast->next->next,只需要判断一次非空。 -
走 3 步:
fast->next->next->next,得小心翼翼地多判断几次空指针,代码写起来恶心。
-
-
收敛速度:
-
相对速度为 1(走 2 步)是最稳的,它一定能“逐个遍历”所有可能性。
-
相对速度越大,虽然理论上能追上,但可能会在环里多绕好几圈(因为一直在跳跃),效率不一定比走 2 步高。
-
228



被折叠的 条评论
为什么被折叠?



