见 leetcode 142题——环形链表 II,自己先写了一遍,发现和《代码随想录》代码差不多,但感觉《代码随想录》解释讲的还是很绕,所以将自己的推导写一下,方便大家理解。首先该题需先判断是否有闭环,然后再查找环的入口,示意图如下:
(该图引用自代码随想录)
1. 判断闭环
采用快慢指针,快指针一次走两步,慢指针一次走一步,则若有闭环,一定会在闭环中相遇,原因如下:
假设头结点到环入口长度为
x
x
x,环入口到相遇点长度为
y
y
y,相遇点到环入口长度为
z
z
z,假设相遇时间为
t
(
t
≥
x
)
t(t\ge x)
t(t≥x),则
slow
\text{slow }
slow 走了
t
t
t 个结点,
fast
\text{fast }
fast 走了
2
t
2t
2t 个结点,而环的周期为
y
+
z
y+z
y+z,则根据关系式有
t
+
k
(
y
+
z
)
=
2
t
t
=
k
(
y
+
z
)
\begin{aligned} t+k(y+z)&=2t\\ t&=k(y+z) \end{aligned}
t+k(y+z)t=2t=k(y+z)
其中
k
k
k 为
fast
\text{fast }
fast 比
slow
\text{slow }
slow 多走的圈数,显而易见
k
≥
1
k\ge1
k≥1,则当
k
k
k 为
1
1
1 时,
t
t
t 取得最小值为
y
+
z
y+z
y+z,而
x
≤
t
min
=
y
+
z
≤
x
+
y
+
z
x\le t_{\text{min}}=y+z\le x+y+z
x≤tmin=y+z≤x+y+z
即第一次相遇时,
slow
\text{slow }
slow 至多走完一圈(取决于
x
x
x 是否为0)。因此
slow
\text{slow }
slow 和
fast
\text{fast }
fast 指针必定会相遇,且
slow
\text{slow }
slow 指针此时还在第一圈,
fast
\text{fast }
fast 指针已经到第二圈。
2. 查找环入口
所以程序在实现时,优先判断有没有环,若存在环,则此时 slow 走过的节点数为
y
+
z
y+z
y+z 个。而 slow 实际走过的节点数为
x
+
y
x+y
x+y 个,因此根据关系式有
y
+
z
=
x
+
y
x
=
z
\begin{aligned} y+z&=x+y\\ x&=z \end{aligned}
y+zx=x+y=z
即当前
slow
\text{slow }
slow 指针到达环入口和从
head
\text{head }
head 到达环入口的节点个数相同,那么此时想要查找环入口只需要将
fast
\text{fast }
fast 指针指向
head
\text{head }
head 节点,然后两个指针一起按一步移动,再次相遇处即环入口。
复杂度分析:由于
slow
\text{slow }
slow 和
fast
\text{fast }
fast 只取第一次相遇,走过两次链表+
x
x
x个节点长度,因此复杂度为
O
(
N
)
O(N)
O(N)。
代码
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def detectCycle(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
# dummy_node = ListNode(0)
# dummy_node.next = head
slow, fast = head, head # 快慢指针指向头结点
while fast and fast.next:
slow = slow.next # 慢指针走一步
fast = fast.next.next # 快指针走两步
if slow == fast: # 两指针相遇
fast = head # 将快指针指向头结点
while slow != fast: # fast和slow一起以一步走,再次相遇处即环入口处
slow = slow.next
fast = fast.next
return slow
return None