原题
题目链接
Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
To represent a cycle in the given linked list, we use an integer pos which represents the position (0-indexed) in the linked list where tail connects to. If pos is -1, then there is no cycle in the linked list.
Note: Do not modify the linked list.
Example 1:
Input: head = [3,2,0,-4], pos = 1
Output: tail connects to node index 1
Explanation: There is a cycle in the linked list, where tail connects to the second node.
例子只列一个,这题的介绍把我弄的有点晕,总之就是写一个函数,函数会接收一个Linkedlist,判断这个Linkedlist中有没有环,并反悔环起始的node,上例中对应的就是2这个节点(吐槽一下Example没有很好的介绍清楚函数的输入和输出,搞得我疑惑了两分钟)
我看了讨论区的几个讨论解释的都不是很清楚,所以这篇我会解释详细一点。
总结
在这题中,我们要解决两个问题:
- LinkedList中是否包含环
- 如果包含环,环的起点在哪里
这里,我只讨论空间复杂度为O(1)的解法,不考虑空间复杂度用一个Set就可以很容易得到答案。
第一步:解决第一个问题,我们可以从Linked List Cycle I找到答案,这里我直接说方案: 使用两个指针,一个slow每次前进一步,一个fast每次前进两部。如果在前进的过程中slow和fast重合,那么就说明他们有环。
第二步:当我们断定有环的时候,如何得到环的入口位置。
假设如下情况
图中点的含义:
S:链表的起点
L:循环的起点
M:相遇的点
定义如下距离
x1 : S到L
x2 : L到M
x3 : M到L
那么有slow此时步长:x1 + r(x2 + x3) + x2 ; fast此时步长:x1 + n(x2 + x3) + x2, 其中r为slow绕圈的次数,n为fast绕圈的次数
根据 2 * slow = fast有:
2x1 + r(x2 + x3) + 2x2 = x1 + n(x2 + x3) + x2
化简:x1 = (n - r - 1) (x2 + x3) + x3
上式说明,一个点从M开始走x1步,会到达L。也就说说我们可以设置两个指针从S,M开始,每次都一步,当两个指针相等时,就会落在L点
代码:
# 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
"""
slow = fast = head
while True:
if fast and fast.next:
fast = fast.next.next
else:
return None
slow = slow.next
if fast == slow:
break
p1 = head
p2 = slow
while p1 != p2:
p1 = p1.next
p2 = p2.next
return p1
简单分析一下复杂度
分两种情况:
- slow到L点时刚好和fast相遇
在N内相遇。 - slow和fast相遇在环的起点之后
在这种情况下,slow遍历会在到达N之前结束
L-N的长度设为K, 假设slow第一次到达L,因为slow和fast此时没有相遇,可以断定fast一定在(L,N]的区间。此时fast于slow的距离必然小于等于N-L,fast和slow的步差为1,那么在N-L步之内,slow和fast必然相遇。假设最大再走N-L步,slow最多也只能达到N。
可以看出算法可以在 2N时间内完成(N为List长度),故时间复杂度O(N)