繁体   English   中英

在循环链表中查找循环起始节点是如何工作的?

How does finding a cycle start node in a cycle linked list work?

提示:本站收集StackOverFlow近2千万问答,支持中英文搜索,鼠标放在语句上弹窗显示对应的参考中文或英文, 本站还提供   中文繁体   英文版本   中英对照 版本,有任何建议请联系yoyou2525@163.com。

我知道龟兔赛跑的结论是循环的存在,但是如何将乌龟移动到链表的开头,同时将兔子保持在会合点,然后一次移动一步,让他们在起点相遇周期?

23 个回复

让我试着用我自己的话来阐明http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare上提供的循环检测算法。

画画

这个怎么运作

让我们有一只乌龟和一只兔子(指针的名称)用一个循环指向列表的开头,如上图所示。

让我们假设,如果我们一次移动乌龟 1 步,一次移动 2 步,它们最终会在某个点相遇。 让我们首先证明这个假设是正确的。

该图说明了一个带有循环的列表。 循环的长度为n ,我们最初离循环有m步。 还假设交汇点距离循环开始点k步,当乌龟走完i步后,乌龟和野兔相遇。 (到那时,Hare 将总共采取2i步。)。

必须满足以下 2 个条件:

1) i = m + p * n + k

2) 2i = m + q * n + k

第一个说乌龟移动i步,在这些i步中它首先进入循环。 然后它经过循环p次以获取某个正数p 最后它又遍历了k个节点,直到遇到野兔。

野兔也是如此。 它移动2i步,在这2i步中它首先进入循环。 然后它通过循环q次获得某个正数q 最后它又遍历了k个节点,直到遇到乌龟。

由于兔子以两倍于乌龟的速度行进,因此当它们到达交汇点时,两者的时间都是恒定的。

所以通过使用简单的速度、时间和距离关系,

2 ( m + p * n + k ) = m + q * n + k

=> 2m + 2pn + 2k = m + nq + k 

=>  m + k = ( q - 2p ) n

在 m、n、k、p、q 中,前两个是给定列表的属性。 如果我们能证明至少有一组 k、q、p 值使这个等式成立,我们就表明假设是正确的。

一组这样的解决方案如下:

p = 0

q = m

k = m n - m

我们可以验证这些值的工作方式如下:

m + k = ( q - 2p ) n  

=> m + mn - m = ( m - 2*0) n

=> mn = mn.

对于这个集合, i

i = m + p n + k

=> m + 0 * n + mn - m = mn.

当然,您应该看到这不一定是最小的 i 可能。 也就是说,龟兔可能已经见过很多次了。 但是,由于我们证明它们至少在某个时刻相遇一次,因此我们可以说该假设是正确的。 因此,如果我们将其中一个移动 1 步,而另一个移动 2 步,它们将不得不相遇。

现在我们可以进入算法的第二部分,即如何找到循环的开始。

周期开始

一旦乌龟和兔子相遇,让我们将乌龟放回列表的开头,并将兔子放在它们相遇的地方(距离循环开始的 k 步)。

假设是,如果我们让他们以相同的速度移动(双方各走一步),他们第一次再次相遇将是循环的开始。

让我们来证明这个假设。

让我们首先假设某个 oracle 告诉我们 m 是什么。

然后,如果我们让它们移动 m + k 步,乌龟必须到达它们最初相遇的点(距循环开始 k 步 - 见图)。

之前我们证明了m + k = (q - 2p) n

由于 m + k 步是循环长度 n 的倍数,因此野兔同时会经历循环 (q-2p) 次并返回到同一点(距离循环开始的 k 步)。

现在,不是让他们移动 m + k 步,如果我们让他们只移动 m 步,乌龟会到达循环的开始。 Hare 距离完成 (q-2p) 旋转还差 k 步。 由于它在循环开始前 k 步开始,因此野兔必须到达循环开始。

结果,这解释了他们第一次必须在经过一些步数后的循环开始时相遇(这是第一次,因为乌龟在 m 步后才到达循环,它永远看不到已经在的兔子循环)。

现在我们知道我们需要移动它们直到它们相遇的步数结果是从列表的开始到循环开始的距离,m。 当然,算法不需要知道m是什么。 它只会一步一步地移动乌龟和兔子,直到它们相遇。 交汇点必须是循环起点,步数必须是到循环起点的距离 (m)。 假设我们知道列表的长度,我们也可以计算从列表长度中减去 m 的循环长度。

参考这张图片:

在此处输入图片说明

相遇前slowPointer 所走的距离= x + y

fastPointer 相遇前的距离= (x + y + z) + y = x + 2y + z

由于fastPointer行驶过程slowPointer两倍的速度,时间是两个常量,当到达会合点。

所以通过使用简单的速度、时间和距离关系 2(x+y)= x+2y+z => x+2y+z = 2x+2y => x=z

因此,通过将slowPointer移动到链表的开头,并使slowPointer 和fastPointer 一次移动一个节点,它们都具有相同的覆盖距离

它们将到达链表中循环开始的点。

这是Floyd 的循环检测算法 您是在询问算法的第二阶段——一旦您找到了一个属于循环的节点,那么如何找到循环的开始

在弗洛伊德算法的第一部分中,兔子每走一步就移动两步。 如果乌龟和兔子相遇,则存在一个循环,并且相遇点是循环的一部分,但不一定是循环中的第一个节点。

当乌龟和兔子相遇时,我们已经找到了最小的 i(乌龟走的步数),使得 X i = X 2i 让 mu 表示从 X 0到循环开始的步数,让 lambda 表示循环的长度。 然后 i = mu + a lambda,和 2i = mu + b lambda,其中 a 和 b 是整数,表示乌龟和兔子在循环中走了多少次。 从第二个方程中减去第一个方程得到 i = (ba)*lambda,所以 i 是 lambda 的整数倍。 因此,X i + mu = X mu X i代表龟兔的交汇点。 如果你将乌龟移回起始节点 X 0 ,让乌龟和兔子以相同的速度继续前进,经过 mu 额外的步骤,乌龟将达到 X mu ,兔子将达到 X i + mu = X mu ,所以第二个交汇点表示循环的开始。

Old Monk 的简单且未被投票的答案解释了当快速跑步者仅完成单个完整循环时找到循环的原因。 在这个答案中,我解释了快跑者在慢跑者进入循环之前多次运行循环的情况。


使用相同的图像: 在此处输入图片说明

假设快跑者在慢速和快速相遇之前已经运行了m次循环。 这意味着:

  • 慢跑距离: x + y
  • 快速奔跑的距离: x + m(y + z) + y即他们相遇的额外y

由于快跑的速度是慢跑的两倍,而且他们跑的时间相同,这意味着如果我们慢跑的距离加倍,我们就会得到快跑的距离。 因此,

  • 2(x + y) = x + m(y + z) + y

求解 x 给出,

x = (m - 1)(y + z) + z

在实际场景中,这意味着x = (m - 1)完整循环运行 + 额外距离z

因此,如果我们将一个指针放在列表的开头,而将另一个指针放在会合点,那么以相同的速度移动它们将导致 in 循环指针完成m - 1次循环,然后遇到另一个指针在循环开始处。

这非常非常简单。 您可以考虑相对速度。 如果兔子移动两个节点,乌龟移动一个节点,相对于乌龟,兔子移动一个节点(假设乌龟静止)。 所以,如果我们在循环链表中移动一个节点,我们肯定会在那个点再次相遇。

找到循环链表内部的连接点后,现在问题就归结为寻找两个链表的交点问题。

图1

在第一次碰撞时,乌龟移动了m+k步,如上图所示。 兔子的移动速度是乌龟的两倍,这意味着兔子移动了2(m+k)步。 从这些简单的事实我们可以得出以下图表。

图1

此时,我们将乌龟移回起点,并声明兔子和乌龟都必须一次移动一步。 根据定义,在m步之后,乌龟将处于循环的开始。 野兔会在哪里?

野兔也将处于周期的开始。 这是从第二张图明确:当乌龟被移回开始,兔子是k步到它的最后一个周期。 m步之后,兔子将完成另一个循环并与乌龟相撞。

方法:

有两个指针:

  • 一次移动一个节点的慢指针。
  • 一次移动两个节点的快速指针。

如果两个指针相遇,则证明存在循环。 一旦它们相遇,其中一个节点将指向头部,然后同时进行一个节点。 他们将在循环开始时相遇。

理由:当两个人沿着圆形轨道走时,其中一个人的速度是另一个人的两倍,他们在哪里相遇? 正是他们开始的地方。

现在,假设快跑者在n步圈中领先k步。 他们会在哪里见面? 正好在nk步。 当慢跑者完成(nk)步时, (nk)者将完成k+2(nk)步。 即, k+2n-2k步,即2n-k)。 (nk)步(路径是圆形的,我们不关心他们相遇的回合数;我们只关心他们相遇的位置)。

现在,快跑者最初是如何领先k步的? 因为慢跑者花了很多步才到达循环的起点。 所以循环的开始是从头节点开始的 k 步。

注意:其中两个指针满足是节点k步骤从循环的开始离开(在循环内)和所述头部节点也是k步骤从循环开始了。 因此,当我们让指针从 bot 这些节点以 1 步的相同速度前进时,它们将在循环开始时相遇。

我相信这很简单。 如果任何部分有歧义,请告诉我。

好的,让我们假设兔子和乌龟在距离循环开始 k 步的点相遇,循环开始前的步数为 mu,循环的长度为 L。

所以现在在集合点 ->

乌龟覆盖的距离 = mu + a*L + k - 公式 1

(到达循环开始所采取的步骤 + 覆盖循环的“a”次迭代所采取的步骤 + 从循环开始的 k 步)(其中 a 是某个正常数)

野兔走过的距离 = mu + b*L + k - 公式 2

(到达循环开始所采取的步骤 + 覆盖循环的“b”次迭代所采取的步骤 + 从循环开始的 k 步)(其中 b 是某个正常数且 b>=a)

所以兔子所覆盖的额外距离是 = 方程 2 - 方程 1 = (ba)*L

请注意,这个距离也等于乌龟到起点的距离,因为兔子的移动速度是乌龟的 2 倍。 如果我们不包括循环的多次遍历,这可以等同于“mu+k”,这也是会合点与起点的距离。

因此,mu + k = (ba)*L

因此,从这一点开始的 mu 步将导致回到循环的开始(因为从循环开始的 k 步已经被用于到达会合点)。 这可能发生在同一周期或任何后续周期中。 因此,现在如果我们将乌龟移动到链表的开头,需要 mu 步才能到达循环的起点,而兔子也需要 mu 步才能到达循环的开头,因此它们将在循环的起点。

PS 老实说,我脑子里和原来的海报有同样的问题,我读了第一个答案,他们确实清除了一些东西,但我无法清楚地得到最终结果,所以我尝试以自己的方式去做,发现它更容易理解。

将问题简化为循环问题,然后回到最初的问题

我觉得下面的解释更直观。

  1. 取从头部 ( O ) 开始的两个指针 ( 1 = 乌龟和2 = 野兔), 1的步长为12的步长为2 想想1到达该循环的起始节点 ( A ) 的那一刻。

    我们想回答以下问题“当 1 在 A 中时,2 在哪里?” .

    所以, OA = a是一个自然数( a >= 0 )。 但它可以写成以下方式: a = k*n + b ,其中a, k, n, b are natural numbers

    • n = 周期长度
    • k >= 0 = 常数
    • 0 <= b <= n-1

    这意味着b = a % n

    例如:如果a = 20 and n = 8 => k = 2 and b = 4因为20 = 2*8 + 4

    1所覆盖的距离是d = OA = a = k*n + b 但同时, 2涵盖D = 2*d = d + d = OA + d = OA + k*n + b 这意味着当2在 A 中时,它必须覆盖k*n + b 正如你所看到的, k是圈数,但是那些圈后,2会是B远离A.所以,我们发现其中2是当1是A.我们叫点B ,其中AB = b

    在此处输入图片说明

  2. 现在,我们将问题简化为一个圆圈。 问题是“集合点在哪里?” . 那个C在哪里?

    在此处输入图片说明

    在每一步中, 2 都减少了从11的距离(比方说米),因为1越来越远离21 ,但同时2更接近12

    因此,交点将是当12之间的距离为零时。 这意味着2减少了n - b距离。 为了实现这一点, 1将进行n - b步,而2将进行2*(n - b)步。

    所以,交点将是n - b远离A (顺时针),因为这是1所覆盖的距离,直到它遇到2 => CA之间的距离是CA = b ,因为AC = AB + BC = n - bCA = n - AC 不要认为AC = CA ,因为AC距离不是一个微不足道的数学距离,它是AC之间的步数(其中A是起点, C是终点)。

  3. 现在,让我们回到初始模式。

    我们知道a = k*n + bCA = b

    我们可以采用 2 个新的指针1'1'' ,其中1'从头部( O )开始, 1''从交点( C )开始。

    1'OA 时1''CA并继续完成k圈。 所以,交点是A

    在此处输入图片说明

    在此处输入图片说明

使用高中教授的相对速度概念的简单解释 - 物理 101 / 运动学讲座。

链表中的圆

  1. 让我们假设从链表的起点到圆的起点的距离是x跳。 我们将圆的起点称为X点(大写 - 见上图)。 还假设圆的总大小为 N 跳。

  2. 兔子的速度 = 2 * 乌龟的速度。 所以这分别是1 hops/sec2 hops/sec

  3. 当乌龟到达圆X的起点时,兔子必须在图中的Y点进一步跳x步。 (因为兔子的距离是乌龟的两倍)。

  4. 因此,从 X 到 Y 的顺时针剩余弧的长度将为Nx 这也恰好是兔子和乌龟之间能够相遇的相对距离 假设这个相对距离将在时间t_m即相遇时间。 相对速度是(2 hops/sec - 1 hops/sec)1 hops/sec 因此使用,相对距离 = 相对速度 X 时间,我们得到, t = Nx秒。 所以到达乌龟和兔子的交汇点需要Nx

  5. 现在在Nx秒的时间内以1 hops/sec速度,较早在X点的乌龟将经过 Nx 跳到达会合点M 因此,这意味着会合点M位于从X = 逆时针Nx跳处(这进一步暗示)=> 从点MX顺时针剩余x距离。

  6. 但是x也是从链表的起点到X点的距离。

  7. 现在,我们不关心x对应的跳数是多少。 如果我们将一只乌龟放在 LinkedList 的开头,将一只乌龟放在会合点M并让它们跳跃/行走,那么它们将在X点相遇,这就是我们需要的点(或节点)。

- 循环前有 k 步。 我们不知道 k 是什么,也不需要找出来。 我们可以只用 k 抽象地工作。

--经过k步

----- T 在循环开始

----- H 是 k 步进入循环(他总共走了 2k,因此 k 进入循环)

** 他们现在是loopsize - k 分开

(注意 k == K == mod(loopsize, k) -- 例如,如果一个节点是 2 步进入 5 节点循环,它也是 7、12 或 392 步,所以循环有多大 wrt k 不考虑到。

由于它们以每单位时间 1 步的速度相互追赶,因为一个移动的速度是另一个的两倍,因此它们将在循环大小 - k 处相遇。

这意味着需要 k 个节点才能到达循环的起点,因此从头到循环起点和碰撞到循环起点的距离是相同的。

所以现在在第一次碰撞后将 T 移回头部。 如果您以 1 的速率移动,T 和 H 将在循环开始时相遇。 (两者都在 k 步中)

这意味着算法是:

  • 从头部移动 T = t.next 和 H.next.next 直到它们碰撞( T == H)(有一个循环)

//通过计算循环的长度来处理当 k=0 或 T 和 H 在循环的开头相遇时的情况

-- 用计数器移动 T 或 H 来计算循环的长度

-- 将指针 T2 移动到列表的头部

--move 循环步长的指针长度

--将另一个指针 H2 移动到头部

--同时移动 T2 和 H2 直到它们在循环开始时相遇

就是这样!

在此处输入图片说明

如图所示,如果指针在P点相遇,距离Z+Y就是P点,X+Y也是P点,即Z=X。 这就是为什么保持从 P 移动一个指针并将另一个从 start(S) 移动到它们相遇的原因,这意味着将相等的距离(Z 或 X)移动到同一点 M(距离 P 的距离 Z 和距离 S 的 X 的距离)将是循环的开始。 简单的!

在此处输入图片说明 形象信用

将指针跟随的链接数称为距离,并计算算法将慢指针移动一个链接并将快速指针移动两个链接所需的迭代次数。 在长度为 C 的循环之前有 N 个节点,标记为循环偏移 k=0 到 C-1。

为了到达循环的开始,slow 需要 N 时间和距离。 这意味着快速在循环中需要 N 距离(N 到达那里,N 旋转)。 所以在时间 N,slow 处于周期偏移 k=0,fast 处于周期偏移 k=N mod C。

如果 N mod C 为零,则慢速和快速现在匹配并且在时间 N 和循环位置 k=0 找到循环。

如果 N mod C 不为零,那么快现在必须赶上慢,在时间 N 是循环中落后 C-(N mod C) 距离。

由于快移动每慢 1 移动 2,每次迭代将距离减少 1,因此这需要的额外时间与时间 N 时快与慢之间的距离一样多,即 C-(N mod C)。 由于慢是从偏移量 0 开始移动,这也是它们相遇的偏移量。

因此,如果 N mod C 为零,则阶段 1 在循环开始时经过 N 次迭代后停止。 否则,阶段 1 在 N+C-(N mod C) 次迭代后停止,偏移量 C-(N mod C) 进入循环。

// C++ pseudocode, end() is one after last element.

int t = 0;
T *fast = begin();
T *slow = begin();
if (fast == end()) return [N=0,C=0];
for (;;) {
    t += 1;
    fast = next(fast);
    if (fast == end()) return [N=(2*t-1),C=0];
    fast = next(fast);
    if (fast == end()) return [N=(2*t),C=0];
    slow = next(slow);
    if (*fast == *slow) break;
}

好的,所以阶段 2:slow 需要多 N 步才能进入循环,此时快速(现在每个时间步移动 1)在 (C-(N mod C)+N) mod C = 0。所以他们相遇在第 2 阶段之后的周期开始时。

int N = 0;
slow = begin();
for (;;) {
    if (*fast == *slow) break;
    fast = next(fast);
    slow = next(slow);
    N += 1;
}

为完整起见,第 3 阶段通过在循环中再次移动来计算循环长度:

int C = 0;
for (;;) {
    fast = next(fast);
    C += 1;
    if (fast == slow) break;
}

对此已经有很多答案,但我曾经为此想出了一个图表,这对我来说在视觉上更直观。 也许它可以帮助其他人。

对我来说主要的啊哈时刻是:

  • T (乌龟)拆分为T1 (预循环)和T2 (循环内)。 T = 乌龟,H = 野兔

  • H 中减去T ,它们在视觉上重叠。 剩下的 ( H - T = H' ) 等于T

  • 剩下的数学很简单。 从 H 中减去 T 在视觉上重叠的地方

通过上述所有分析,如果您是一个以身作则的人,我尝试编写一个简短的分析和示例,以帮助解释其他人试图解释的数学。 开始了!

分析:

如果我们有两个指针,一个比另一个快,并将它们一起移动,它们最终将再次相遇以指示循环或空值指示没有循环。

要找到循环的起点,让...

  1. m是从头部到循环开始的距离;

  2. d是循环中的节点数;

  3. p1是较慢指针的速度;

  4. p2是较快指针的速度,例如。 2 表示一次通过两个节点。

    观察以下迭代:

 m = 0, d = 10: p1 = 1: 0 1 2 3 4 5 6 7 8 9 10 // 0 would the start of the cycle p2 = 2: 0 2 4 6 8 10 12 14 16 18 20 m = 1, d = 10: p1 = 1: -1 0 1 2 3 4 5 6 7 8 9 p2 = 2: -1 1 3 5 7 9 11 13 15 17 19 m = 2, d = 10: p1 = 1: -2 -1 0 1 2 3 4 5 6 7 8 p2 = 2: -2 0 2 4 6 8 10 12 14 16 18

从上面的样本数据中,我们不难发现,每当快慢指针相遇时,它们都离循环开始有m步之遥。 为了解决这个问题,将较快的指针放回头部并将其速度设置为较慢指针的速度。 当他们再次相遇时,节点是循环的开始。

我不认为这是真的,当他们相遇时,那是起点。 但是是的,如果另一个指针(F)在之前的交汇点处,那么该指针将在循环的末尾而不是循环的开始,而指针(S)将从列表的开头开始结束在循环的开始。 例如:

1->2->3->4->5->6->7->8->9->10->11->12->13->14->15->16->17->18->19->20->21->22->23->24->8

Meet at :16

Start at :8

public Node meetNodeInLoop(){

    Node fast=head;
    Node slow=head;

    fast=fast.next.next;
    slow=slow.next;

    while(fast!=slow){

        fast=fast.next;
        fast=fast.next;

        if(fast==slow) break; 

        slow=slow.next;
    }

    return fast;

}

public Node startOfLoop(Node meet){

    Node slow=head;
    Node fast=meet;

    while(slow!=fast){
        fast=fast.next;
        if(slow==fast.next) break;
        slow=slow.next;
    }

    return slow;
}

可以说,

N[0] is the node of start of the loop, 
m is the number of steps from beginning to N[0].

我们有 2 个指针 A 和 B,A 以 1x 速度运行,B 以 2x 速度运行,两者都从头开始。

当 A 到达 N[0] 时,B 应该已经在 N[m] 中。 (注:A用m步到达N[0],B应该再m步)

然后,A 再跑 k 步与 B 碰撞,即 A 在 N[k],B 在 N[m+2k](注意:B 应该从 N[m] 开始运行 2k 步)

A 分别在 N[k] 和 N[m+2k] 处碰撞 B,这意味着 k=m+2k,因此 k = -m

因此,要从 N[k] 循环回到 N[0],我们还需要 m 个步骤。

简单地说,我们只需要在找到碰撞节点后多运行 m 步即可。 我们可以有一个从头开始运行的指针和一个从碰撞节点运行的指针,它们将在 m 步后在 N[0] 处相遇。

因此,伪代码如下:

1) A increase 1 step per loop
2) B increase 2 steps per loop
3) if A & B are the same node, cycle found, then go to 5
4) repeat from 1
5) A reset to head
6) A increase 1 step per loop
7) B increase 1 step per loop
8) if A & B are the same node, start of the cycle found
9) repeat from 6

使用图表进行处理会有所帮助。 我试图在没有方程的情况下解释这个问题。

  1. 如果我们让兔子和乌龟跑一圈,兔子跑两次乌龟,那么一圈结束时,兔子乌龟跑到一半。 在两圈结束时,兔子乌龟会跑一圈,他们都相遇了。 这适用于所有速度,例如如果兔子跑了 3 次,兔子 1 圈等于乌龟的 1/3,所以在 3 圈结束时,兔子乌龟会跑完 1 圈然后它们相遇。
  2. 现在,如果我们在循环前 m 步启动它们,那么这意味着更快的兔子在循环中提前开始。 因此,如果乌龟到达循环的起点,兔子会提前 m 步循环,当它们相遇时,它将在循环开始前 m 步。

我看到大多数答案对此给出了数学解释“如何将乌龟移动到链表的开头,同时将野兔保持在会面位置,然后一次移动一步,使它们在循环的起点相遇?

以下方法在幕后也执行与弗洛伊德循环检测相同的操作,但原理很简单,但以 O(n) 内存为代价。

我想添加一个更简单的方法/理由来找到循环的开始。 由于在任何地方都没有提到这个方法,我在这里测试了这个: https : //leetcode.com/problems/linked-list-cycle-ii/并且它通过了所有的测试用例。

让我们考虑一下,我们已经获得了 LinkedList 的头部引用。

     public ListNode detectCycle(ListNode head) {
     
            // Consider a fast pointer which hops two nodes at once.
            // Consider a slow pointer which hops one node at once.
            // If the linked list contains a cycle,
            // these two pointers would meet at some point when they are looping around the list.
            // Caution: This point of intersection need not be the beginning of the cycle.
            ListNode fast = null;
            ListNode slow = null;
            if (head != null) {
                if (head.next != null) {
                    fast = head.next.next;
                    slow = head;
                } else {
                    return null;
                }
            }
            while (fast != null && fast.next != null) {
                // Why do we need collection here? Explained below
                Set<ListNode> collection = new HashSet<>();
                if (fast == slow) {
                  // Once the cycle is detected, 
                     we are sure that there is beginning to the cycle.
                  // In order to find this beginning, 
                  // 1. move slow pointer to head and keep fast pointer at 
                        the meeting point.
                  // 2. now while moving slow and fast pointers through a 
                        single hop, store the slow reference in a collection.
                  // 3. Every time you hop the fast pointer, check the fast 
                        pointer reference exits in that collection.
                  // Rationale: After we moved slow pointer to the head, 
                     we know that slow pointer is coming behind the fast
                     pointer, since collection is storing all nodes from the 
                     start using slow pointer, there is only one case we get 
                     that fast pointer exists in the collection when slow 
                     pointer started storing the nodes which are part of the 
                     cycle. Because slow pointer can never go ahead of fast 
                     pointer since fast pointer already has an head-start, at 
                     the same time, the first occurence will always be of the 
                     starting point of the cycle because slow pointer can't 
                     go ahead of fast pointer to store other nodes in the 
                     cycle. So, the moment we first find fast pointer in that 
                     collection means, that is the starting point of the 
                     cycle.
                    slow = head;
                    collection.add(slow);
                    while (!collection.contains(fast)) {
                        slow = slow.next;
                        collection.add(slow);
                        fast = fast.next;
                    }
                    return fast;
                }
                fast = fast.next.next;
                slow = slow.next;
            }
            return null;
        }

在花了两个小时试图阅读所有答案后,我在 leetcode 上找到了这条评论。 可以肯定地说,它拯救了我的夜晚。

https://leetcode.com/problems/linked-list-cycle-ii/discuss/44774/Java-O(1)-space-solution-with-detailed-explanation./44281

在此处输入图片说明

如果您考虑会合点背后的数学原理,实际上很容易证明他们会在起点会合。
首先让m表示链表中循环的起点, n表示循环的长度。 然后为了兔子和乌龟相遇,我们有:

( 2*t - m )%n = (t - m) %n, where t = time (at t = 0 , both are at the start)

更数学地说明这一点:

(2*t - m - (t - m) ) = 0 modulo n , which implies , t = 0 modulo n 

所以他们会在时间t相遇,这应该是周期长度的倍数。 这意味着它们在一个位置相遇,即(tm) modulo n = (0-m) modulo n = (-m) modulo n

所以现在回到这个问题,如果你从链表的开头移动一个指针,另一个从交点移动,经过 m 步后,我们将使野兔(在循环内移动)到达一个点,即((-m) + m) modulo n = 0 modulo n就是循环的起点。将从链表的开头遍历m步。

作为旁注,我们也可以这样计算它们相交的时间:条件t = 0 modulo n告诉我们它们将在周期长度倍数的时间相遇,并且t应该大于m 。因为他们会在循环中相遇。 所以所用的时间将等于n的第一个大于m 的倍数。

假设您的指针在 y 和 z 点相交。

n 和 m 分别是较快和较慢的指针在相遇之前的循环次数。

有关证明的其余部分,请参阅图像。 在链表中找到循环的起点

我知道这个问题已经有一个公认的答案,但我仍然会尝试以流畅的方式回答。 认为 :

The length of the Path is 'X+B' where 'B' is the length of the looped path and X of the non looped path. 
    Speed of tortoise : v
    Speed of hare     : 2*v 
    Point where both meet is at a distance 'x + b - k' from the starting point.

现在,让兔子和乌龟在时间 't' 后相遇。

观察:

如果,乌龟行走的距离 = v*t = x + (bk)(比如说)

然后,野兔行进的距离 = 2*v*t = x + (b - k) + b(因为野兔已经穿过循环部分一次)

现在,会议时间相同。

=> x + 2*b - k = 2* (x + b - k)

=> x = k

这当然意味着未循环的路径的长度与循环起点与两者相遇点的距离相同。

1 循环链表的循环开始节点

我正在尝试实现一个程序来查找循环链表的起始节点。 我的代码是 - 这是我的代码。 我没有得到任何回报t-&gt;数据部分或我无法找到循环墨水列表的起始节点。任何帮助? ...

2 删除循环链表中的节点

我有一个下面试图解决的基本链表问题。 我会很欣赏我的方法,算法的正确性(甚至编码风格)方面的任何投入。 该问题需要一个函数,该函数删除循环链接列表中所有出现的int并从列表中返回任何节点或返回NULL(当列表为null时)。 到目前为止,这里有一些C ++代码: ...

3 如何在循环链表中添加节点?

我的目标是从带有动态数组的文件中读取文本,并使用参数“numline”打印出你想要的任何行。 我必须使用循环单链表来完成这项工作。 当我运行函数时,我只得到第一行X numline。 我很困惑,我是否在添加节点分区或遍历和打印输出分区时出错? EDiT:我编辑了文件...... ...

4 如何从循环链表中删除节点?

我是Java的初学者,由于复杂而无法理解Java中的链表。 所以我的代码很简单。 这是班 我了解该算法的工作原理。 搜索一个节点,当找到它时,将其之前的节点指向被搜索节点之后的节点。 每当我搜索节点时,都会得到java.lang.nullpointer异常,这些异常基 ...

5 Java循环链表,删除节点无法正常工作

好的,所以我需要从循环列表中删除项目,这是一个较大的程序的一部分,该程序无法正常工作,而且我似乎无法删除传入删除方法的最后一个节点,如果传入的索引为1,它将删除第一个节点列表中的节点并替换它,但是当此时仅剩一个节点时,没有什么可引用的。 我将在此处保留删除方法 } ...

6 从循环链表中删除

我正在研究从循环链表中删除的代码。 在处理此代码时,我意识到删除列表的第一个元素时出错,但我找不到如何修复此错误,因为我不完全了解该主题。 帮助解决这个问题 结果必须是这样的 但结果是这样的 感谢回答@ashu。 我加了退货。 ...

7 C中的循环链表

如何删除单循环链表中的所有节点? 这段代码正确吗? 我尝试时没有在代码块中得到输出。 怎么了 ...

2018-03-31 00:34:40 1 65   c
8 Swift中的循环链表

我想确定我的链表是否为空,但是,我无法通过检查head.next == tail来做到这一点,因为我会收到一个错误,即二进制运算符“ ==”无法应用于'LLNode类型的操作?”。 ...

9 Java中的循环链表

我是Java的新手,正在从事具有链表的作业。 给我一个测试器类,并且只将我的代码插入链接列表类中的特定位置。 首先,我面临的问题是我无法打印列表并查看我的代码是否正常工作或是否取得了任何进展。 测试器文件使用“ printList(nameOftheList)”,但不打印列表中的任何元素。 ...

10 可可中的循环链表

是否有类似于Cocoa中可用的循环链表的东西? 我知道NSArray是有序的 - 但我认为我不能使用'nextItem'或'previousItem' - 对吗? 此外,我需要将最后一项的nextItem作为第一项。 我可以添加自己的nextItem和previousItem方法, ...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2022 STACKOOM.COM