
题目
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。 说明:不允许修改给定的链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。
❝来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/linked-list-cycle-ii
❞
题解
方法 1:哈希表
想法
如果我们用一个 Set 保存已经访问过的节点,我们可以遍历整个列表并返回第一个出现重复的节点。
算法
首先,我们分配一个 Set 去保存所有的列表节点。我们逐一遍历列表,检查当前节点是否出现过,如果节点已经出现过,那么一定形成了环且它是环的入口。否则如果有其他点是环的入口,我们应该先访问到其他节点而不是这个节点。其他情况,没有成环则直接返回 null 。
public class Solution {
public ListNode detectCycle(ListNode head) {
Set<ListNode> visited = new HashSet<ListNode>();
ListNode node = head;
while (node != null) {
if (visited.contains(node)) {
return node;
}
visited.add(node);
node = node.next;
}
return null;
}
}
复杂度分析
时间复杂度:O(n) 不管是成环还是不成环的输入,算法肯定都只会访问每个节点一次。对于非成环列表这是显而易见的,因为第 n 个节点指向 null ,这会让循环退出。对于循环列表, if 条件满足时会导致函数的退出,因为它指向了某个已经访问过的节点。两种情况下,访问的节点数最多都是 n 个,所以运行时间跟节点数目成线性关系。
空间复杂度:O(n) 不管成环或者不成欢的输入,我们都需要将每个节点插入 Set 中一次。两者唯一的区别是最后访问的节点后是 null 还是一个已经访问过的节点。因此,由于 Set 包含 n 个不同的节点,所需空间与节点数目也是线性关系的。
方法 2:Floyd 算法
想法
结合双指针解法,根据规律做出算法
算法
设长度如下

快指针距离是慢指针距离的两倍,可计算得,F=b,所以当慢指针和快指针「第一次相聚后」,「将快指针指向初始位置」,并降低速度为「1步每次」,当慢指针和快指针「第二次相聚」的时候的节点即为入圈节点;

public class Solution {
private ListNode getIntersect(ListNode head) {
ListNode tortoise = head;
ListNode hare = head;
while (hare != null && hare.next != null) {
tortoise = tortoise.next;
hare = hare.next.next;
if (tortoise == hare) {
return tortoise;
}
}
return null;
}
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
ListNode intersect = getIntersect(head);
if (intersect == null) {
return null;
}
ListNode ptr1 = head;
ListNode ptr2 = intersect;
while (ptr1 != ptr2) {
ptr1 = ptr1.next;
ptr2 = ptr2.next;
}
return ptr1;
}
}
复杂度分析
时间复杂度:O(n)
空间复杂度:O(1)