题目
给定一个链表的头节点 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 <= 105pos的值为-1或者链表中的一个有效索引
进阶: 你是否可以使用 O(1) 空间解决此题?
题解
解题思路
这道题要做出来并不难,最直接的方法是遍历链表时使用一个 Set 存储遍历过的元素,对于每个节点都判断是否已经在 Set 集合中,如果是则该节点为入环点,这种方法比较简单,这里就不再给出代码。该解法的缺点是空间复杂度是O(N),下面着重介绍空间复杂度为 O(1) 的快慢指针解法。
我们可以使用两个指针,分别为快指针 fast 和慢指针 slow,最开始两者都指向 head,接下来 slow 每次向后移动一个节点,fast 每次向后移动两个节点,如果链表中有环,那么两者最终肯定会相遇。但是两者是不一定会在入环点相遇的,相遇后又应该如何进一步确定入环点呢?
假设从 head 到入环点的节点数为 a,从入环点到相遇点的节点数为 b,然后从相遇点再往后重新回到入环点的节点数为 c,很容易可以得到,在相遇时,slow 走过的节点数为 a + b,而 fast 走过的节点数为 a + n*(b + c) + b,其中 n 为 fast 在环中走过的圈数。
然后,因为在任意时候,fast 走过的距离都是 slow 的两倍,所以可以得到 2*(a + b) = a + n*(b + c) + b,进一步化简为 a = (n-1)b + nc,进而得到 a = (n-1)*(b+c) + c,从中可以看出, a 的距离等于 n-1 圈环加上 C 的距离,因此从相遇点后,使用指针 p 从 head 出发,而 slow 从相遇点出发,每次移动一个节点,最终两者一定会在入环点相遇,即可得到入环点。
代码
Java 题解
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
// 慢指针
ListNode slow = head;
// 快指针
ListNode fast = head;
while(fast != null) {
slow = slow.next;
fast = fast.next;
if (fast != null) {
fast = fast.next;
}
if (fast != null && slow == fast) {
// 快慢指针相遇
ListNode p = head;
// 寻找入环点
while(p != slow) {
p = p.next;
slow = slow.next;
}
return p;
}
}
return null;
}
}
Go 题解
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func detectCycle(head *ListNode) *ListNode {
slow, fast := head, head
for fast != nil {
slow = slow.Next
if fast.Next == nil {
return nil
}
fast = fast.Next.Next
if fast == slow {
p := head
for p != slow {
p = p.Next
slow = slow.Next
}
return p
}
}
return nil
}
复杂度分析
- 时间复杂度:O(N)
其中 N 是链表的节点数。 - 空间复杂度:O(1)
这种解法没有多余的空间开销,只使用了 slow、fast、p 三个指针。