给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。
提示:
链表中节点的数目范围在范围 [0, 104]
内
-105 <= Node.val <= 105
pos
的值为 -1
或者链表中的一个有效索引
该题其实可以拆分成两问,一是判断链表中有没有环,另一个是如果有环的话则需要找到入环口。
下面我们先来看一下第一个问题,如何判断链表中是否有环。
我们可以把这个问题换一种情境来看,现在假设我要和一名跑步运动员赛跑。我们在相同的起跑线上开始跑步,那么跑步速度很慢的我肯定一开始就会被超过。而且如果我们是在一条直跑道比赛的话,那我们是不是很显然就没有再次相遇的机会了。如下:
那如果我们是在操场的跑道上比赛呢,操场的跑道是一个环形,那么即使我一开始被运动员超越了,运动员依然有可能绕我一圈再超越我一次,是不是就能够相遇了?
同样的思路,我们可以在链表中设置一快一慢两个指针,一个指针遍历的快,另一个指针遍历的慢,那么如果链表中有环的话,有可能两个指针就能在环中相遇了。注意,我这里说的是有可能。因为在跑道上相遇是必然的事情,这是一个连续的过程,我和运动员肯定会在某一时刻在环中相遇的;但链表中指针的遍历却是离散化的,如果快指针一次走三个节点,而慢指针一次走一个节点,那么有可能两个指针永远没有相遇的时候,快指针只会一圈又一圈的跳过慢指针。
我们如何确保两个指针一定会相遇呢?很简单,让快指针一次只走两个节点,慢指针一次走一个节点,这样是不是就相当于在环中,快指针正在以一个节点的相对速度向慢指针逼近?这样肯定就会有相遇的时刻了。
第二个问题,如何找到入环处的节点呢?
上面的问题中,我们已经确定了快节点的速度为两个节点每次,慢节点的速度为一个节点每次。那么我们可以把两者走过的节点数通过速度联系起来了。下面我们来画一下路径图:
在上图,设x为链表头到入环处节点的距离,y为入环处节点到两节点相遇位置的距离,z为两节点相遇位置到入环处节点的距离。那么,我们容易得到第一个式子,当两个指针相遇时,快指针已经走了n圈再加上x与y之和;那第二个式子怎么来的呢?慢指针一定连一圈都没有走完就被追上了吗?我们画一个图来看看:
我们把圆环拆成直线来看,l代表一圈的长度,当慢指针进入环时,快指针在这个展开图中一定是位于慢指针之前的,这个比较容易想到,上面我们也得出了相同时间下,快指针走的距离是慢指针的两倍,因此如上图所示,快指针一定会在某个时刻与慢指针相遇,而此时慢指针走不完一圈。因此可以得出,慢指针走的距离为x+y。
将快慢指针的距离式子联系起来,就可以得出上述结论,x=(n-1)(y+z)+z。
这个式子什么意思呢?我们指到,y+z代表的是圆环一圈的长度,而n为相遇时快指针所走过的圈数,是大于等于1的,这个不加证明,只需要想象一个极端情况验证一下即可。x就是我们想要知道的链表头到入环处节点的距离,而z为相遇节点处到入环节点处的距离。
这个式子就说明了一件事,链表头到入环处节点的长度等于从两个节点相遇位置开始绕n-1圈后再加上相遇节点处到入环节点处的长度。简单说就是,新设一个节点,从链表头节点开始,按一次一个节点的速度前进;同时,从两个指针相遇的位置,再新设一个节点按相同的速度前进,两者最终一定会相遇在入环处节点的位置上。因为两者走的距离是一样的。
这个一定要好好理解。
理解了上面两个问题后,代码就容易的多了,我们只需要设置两个指针,进行遍历操作。遍历结束的标志是快指针或者快指针的下一跳指向None,因为这种情况下,链表一定没有环,环状的链表是不会有指向None的情况的。
然后在遍历过程中,如果有快指针和慢指针相同的情况出现,那么就说明两者相遇了,就开始在链表头节点处和两者相遇处新设两个节点,开始以一次一个节点的速度前进,直到相遇,返回相遇处的节点即可。
代码如下:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast = head
slow = head
#根据快指针能不能指到空来判断这是单向链表还是带有环的链表
while fast and fast.next:
fast = fast.next.next #快指针一次走两格
slow = slow.next #慢指针一次走一格
if fast == slow: #如果两个指针能够相遇,就说明是有环的
#确定有环之后,就要在相遇的节点新设一个节点index1
#在头节点新设一个节点index2
#两者同步走,相遇处即为入环口
index1 = fast
index2 = head
#遍历结束条件为两者相遇
while index1 != index2:
index1 = index1.next
index2 = index2.next
#相遇之后返回节点
return index1
return None
感觉这个题好巧妙,后面再来多做做。