本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
前言
这次要啃个硬骨头!!! 网上也有相当多的解题思路,我也看过,但是仍然觉得还有不足之处。当然,十分感谢那些解题思路,以及LeetCode网友 RainbowSecret 的解答。才有以下我的这篇梳理。
题目
Acceptance : 31.3% Difficulty : Medium
Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
Note : Do not modify the linked list.
Follow up :
Can you solve it without using extra space ?
Tags Linked List Two Pointers
题解
首先感谢简书作者 floodiu 对本题的解答。我也是看过他的解答之后才有的我写在他的解答之下的疑问,以及以下这篇思路梳理。
- X 是链表的起点。
- Y 是环的起点。
- Z 是 fast 和 slow 首次相遇的地方(二者同时从X出发,slow 每次移动一步, fast 每次移动两步)
- a,b,c 分别表示 XY(蓝色), YZ(红色), ZY(绿色)的长度。 当 fast 和 slow 在 Z 点 首次相遇时:
fast 移动的距离是: a + b + c + bslow 移动的距离是: a + b
毫无疑问, floodiu 的思路是正确但是不太全面的。 非常容易可见的错误是,当 a 的距离非常长之后, fast 指针将在 cycle 中转过好多圈,也就是 fast 走过的距离并非简单的 a + b + c + b。
除此之外,当我第一次看到这个问题时候的疑问是:
- 怎么证明 fast 在 cycle 中 与 slow 站在同一位置时,他俩是第一次相遇,而非 fast 曾经超越 slow n 次 而后才又站到一起呢?
这个问题的解答可以考虑这样一个问题: fast 首先进入 cycle 是事实,但是我们仍然可以把它看作是 fast 在 slow 之后。 也就是,当 slow 进入 cycle 时, 就变成了两个人在环形跑道上的追击问题了,把从这时起的位置看作是起始位置,可能 slow 在 fast 之前若干距离(记为α)处。但是他俩之间的相对速度是 -1, 也就是说,自然数 α 将通过不断地 α - - ,总会有一刻,它的值为 0,而非一下变成了负数。所以,他俩站上同一位置时,他俩可能真的是第一次相遇。这里我又想到了一种极特殊的情况,就是 slow 在XY上 Y - 1 的位置, fast 在环中ZY上 Y - 1 的位置。那么当 slow 进入环中的时候,fast 刚好超过了一下它进入了 YZ 上 Y + 1 的位置。当然,以这样讨论是不是 fast 超过了 slow,还需要深入考虑具体的情况。但是我们接下来分析的解题思路是与整个这个疑问都无关的!!
真·题解
OK,让我们进入正式的题目解析过程。感谢LeetCode网友 RainbowSecret 的解答。但是我看他的解答还是觉得不太十分清晰。或者,由于语言的问题,理解起来并不十分容易。
首先,上图
- X 为链表起点,Y 为链表中环 cycle 的进入点Entry, Z 为 fast 指针 与 slow 指针 在环中相遇的位置。
- XY 段长度(从X走到Y所需的步数,亦即所需=root->next的操作数)为 a, 弧YZ(红色)段长度为 b,弧ZY(绿色)的长度为 c。
- 于是环的长度为 YZ + ZY 的长度,也就是 b + c,这里我们记为 d = b + c。
- 假设他们相遇时 slow 需要走过 t 步。
以上为所有事实,及我们的变量声明。
首先,当他们相遇时,由于 fast 是 slow 速度的2倍,所以: 2 * t = t + N1 * d。。。。(N1 = 1, 2, 3……)。。。。。。。。。。。① 也就是说,fast 所走的距离 = slow 所走的距离(也就是 a + b) 再加上 fast 自己在环处转的圈。这里 N1 最小只能取到 1,因为当 fast 再次追上 slow 时,fast 无论如何都是要再跨过 一次 Y 点的。否则,fast 将一直处于 slow 之前。
把右侧的 t 移到左侧,于是得出: t = N1 * d。。。。。。② 额,这个结果看上去令人有些惊讶。但,这是事实。
同样的,我们考虑 fast 指针走过的距离的话。相当于 a + N2 * d + b。 或者可以理解为,我们把第一个等式中的右侧的 T, 也就是 slow 走过的距离用 a + b 来代替。 所以有 2 * t = a + N2 * d + b。。。。。。(N2 = 1, 2, 3……)。。。。。③
③ - 2 * ② 得
(2 * N1 - N2 ) * d = a + b。。。。。。。④
又 ∵ d = b + c,把它代入 ④ 可得:
(2 * N1 - N2 - 1 ) * d + b + c = a + b
于是便有: (2 * N1 - N2 - 1) * d + c = a
这等式可以理解为: 直线段XY的步数 等于 在cycle中转 n 圈的步数 加上 弧 ZY (绿色) 段的步数 c。
也就是 a = n * d + c
那么,接下来就可以分析了。
现在 fast 和 slow 相遇在 Z 点,把 slow 拨回到出发点 X, 并且 把 fast 的速度也降为 1 步 1 格。让两者同时出发。 那么, 当 slow 从 X 走到 Y 时,slow 走了个 a 步。 而现在的 fast 跟 slow 的速度是一致的, 它也将会走过 a 步。而刚好恰恰 a 又等于 n 圈加上 出发时 fast 距离 Y 点的距离 c。 所以,当 slow 走到 Y 点的时候, fast 也刚好 转过了 n 圈之后 又回到了 Y 点。
不知道这样的解释大家清楚了没有。
以下是AC的代码:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if (head == NULL)
return NULL;
ListNode *fast = head, *slow = head;
while (fast->next != NULL && fast->next->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow) // 当 slow 和 fast 相遇时
{
slow = head; // 把 slow 拨回到起点
while (fast != slow) // 直到 slow 和 fast 再次相遇
{
fast = fast->next; // 这次让 fast 同样一步一个脚印地走
slow = slow->next;
}
return fast; // 当他们再次相遇,将相遇在环的入口 Y 处
}
}
return NULL;
}
};