单链表与环
前言:
判断链表中是否有环结构存在,是面试一个常见的问题。一般面试官在使出这招的时候,对面试者的期望是,一定要答对如何判断链表中是否有环。因为这个是最烂大街的面试题,只要稍微搜集一下面试题,看过这个题的,都应该可以做出来。如果这个不能回答的话,面试官会对面试者的自学能力、搜集信息的能力以及对面试的重视程度产生严重的怀疑。在第一个热身题回答上来之后,面试官会继续深入提问。有两种深入提问的选择:(1)如何证明你的方法是正确的?(2)如何找到环的入口节点。下面我们就一起把这几个问题总结一下。
- 如何判断链表中是否有环存在?
- 如何证明你的方法是正确的?
- 如果一个链表中有环存在,那么,如何找到这个环的入口节点?
- 如何证明你的方法是正确的?
注意:本文将没有具体代码实现,目的是为了让读者不会深入代码细节中,明白问题的本质以及原理。
如何判断链表中有环结构存在?
很简单。两个指针,一个叫 slow ,一个叫 fast 。同时从链表头节点出发。fast 的移动速度是 2 ,slow 的移动速度是 1 。结束条件有两个:
-
如果 fast 到达了尾部,即 fast == null ,说明链表没环;
-
如果 fast 和 slow 相遇了,即 fast == slow ,也就是两个指针指向了同一个节点,说明有环;
代码比较简单,这里就不用代码进行说明了。重点说说,为什么通过快慢两个指针的方式可以判断链表是否有环呢?这里有两种证明方式。
方式一:相对速度法。
fast 的 速度是 2 ,slow 的速度是 1 ,二者的相对速度是 2 - 1 = 1 。所以,如果有环的话,必定会出现 fast 追上 slow 的情况。
方法二:反证法。
假设有环存在但是 fast 和 slow 不能相遇。考虑某轮 while 循环结束的时候,速度为 2 的 fast 恰好跳过了速度为 1 的 slow 。那么,根据速度倒推回这轮循环开始的时候的 fast 和 slow 的状态,我们发现,fast 的位置和 slow 的位置相同,与 fast 和 slow 不能相遇矛盾。得证。
在已知链表存在环的情况下,如何找到环的入口节点?
方法一:利用环的长度,思维方式简单直接。
先用上文的快慢指针法得出链表有环。然后在相遇点,让 fast 或者 slow 再转一圈,这样就能知道环的长度,记为 x 。然后,让 fast 和 slow 同时回到链表头节点 head ,fast 和 slow 的速度都改成 1 ,敲黑板,是 1 ,二者的速度都是 1 。 然后让 fast 先走 x 步, fast 和 slow 再次相遇的点就是环的入口节点。题外话:这里是为了描述简单,把环的长度记为 x ,我们用代码实现的时候,一定要起一个英文单词作为这个变量的名字,比如叫 circleLength 。不然的话,面试官会觉得你写代码的时候比较随性,不能写出具有良好可读性的代码。
证明:
我们需要证明两点:
- fast 和 slow 会相遇。
- 相遇点就是环的入口节点。
首先,证明二者会相遇。我们采用反证法。
假设二者不能相遇,那么过了足够长的时间后(保证二者都已经上环),那么,考察二者的位置。因为 fast 比 slow 先走 x 步,那么,给定一个 slow 的位置,就能推出 fast 的位置。又因为 x 恰好为环的长度,所以二者在环上的位置相同,即,他们指向同一个节点,与二者不能相遇矛盾。
然后,证明相遇点为环的入口节点。
假设,二者同时在环上且没有相遇,那么,因为二者相对速度为 0 ,所以二者将永远不能相遇。与上面的二者会相遇矛盾。所以,二者在环上的时候,必然已经出于相遇状态。那么,倒推法,找到相遇的“临界点”,必定是 slow 在环外,且下一个节点为环的入口节点,fast 在环内,且下一个节点也是入口节点。得证。