# [142] 环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 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
解释:链表中没有环。
进阶:
你是否可以不用额外空间解决此题?
这道题跟141.环形链表
太像了,唯一的区别就是该题已知存在环,然后要求出环起始的位置。
如果直接用141.环形链表
的方法一做其实是直接AC的,因为第一次找到的已标记的位置一定第一个被二次遍历到的位置,即环的起始位置。
那么问题的难点又在于不使用额外空间了。这里使用到的方法是,若快慢指针相遇了,则将慢指针移动到头结点,然后两指针以相同的速度遍历,再次相遇时的位置即为环的起始位置。
这个题的解法更偏向于数学思维一点,我们可以用数学来证明算法的正确性。
因为快指针走过的距离为慢指针的两倍,设慢指针走过的距离为a,则快指针走过的距离为2a,设环起点距离相遇点的距离为x,可得出起始点到环节点的距离为 a - x
,快指针距离环起点的距离为 2a - a - x
(比慢指针多走的距离减去环起点到相遇点的距离),是相等的。还是不理解的话其实只需要画个图就很清晰了。
function detectCycle(head: ListNode | null): ListNode | null {
// 若链表数量小于等于1,则不可能存在环
if (!head || !head.next) return null;
let quick = head;
let slow = head;
while (quick && quick.next) {
quick = quick.next.next;
slow = slow.next;
if (slow === quick) break;
}
// 若快慢指针不相遇,则说明无环
if (slow !== quick) return null;
// 设慢指针走过的距离为a,环起点距离相遇点的距离为x
// 则:快指针走过的距离为2a,起始点到环起点的距离为 a - x
// 可得:快指针此时距离环起点的距离为 (2a - a) - x(比慢指针多走的距离减去环起点到相遇点的距离) = a - x
// 因此,起始点到环起点的距离一定等于快指针到环起点的距离,只需要步进a-x步,则快指针所在的位置即为环起点。
slow = head;
while (slow !== quick) {
slow = slow.next;
quick = quick.next;
}
// 相遇位置则为环起始位置
return quick;
}