题目描述
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
分析
输入:链表头节点 head
输出:
若有环,返回环的入口节点 cycleEntry;
若无环,返回 null
解题方法
快慢指针(是否有环[juejin.cn/post/703983…]) + 数学分析
首先先用快慢指针判断是否有环
若有环,判断结束时,slow & fast 均指向环中的同一节点
(图片来源 leetcode)
设从 head 到环入口节点 cycleEntry 的距离是 a, 相遇时漫指针在环内走过 b, 此时块指针已经走过了 n 圈。
接下来,需要用数学分析两件事:
- 快慢指针一定会在漫指针绕环的第一圈相遇
如果慢指针进入环内时,快指针在环内走过了 x,那么设环的长度是 n,两个指针的距离是 n - x;
因为两指针速度差距是 1,所以当他们相遇的时候,慢指针走的距离实际上是 n - x,小于环长度 n,所以二者会在第一圈相遇
- a === c, 即
head到cycleEntry的距离,是从两指针相遇处到cycleEntry的距离
相遇时:
slow 走过的距离: d1 = a + b; fast 为 d2 = a + n(b + c) + b
因为速度差一倍,那距离也就差一倍:
所以:2(a + b) = a + n(b + c) + b => a = c + (n - 1)(b + c),可见两者距离一样
代码
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var detectCycle = function (head) {
// 边界: 没有节点,只有一个节点不存在环
if (!head) return null;
if (!head.next) return null;
// 设置变量: 快慢指针
let slow = head,
fast = head;
// 过程:判断是否成环,并使得快慢指针均指向相遇的节点
while (fast && fast.next) {
fast = fast.next.next;
slow = slow.next;
if (fast === slow) {
let tmp = head;
// head,相遇节点一起以相同速度移动,他们相遇的节点就是环入口
while (tmp !== slow) {
tmp = tmp.next;
slow = slow.next;
}
return slow;
}
}
return null;
};
复杂度
时间:O(N),包括检测环,找到环入口两个过程,慢指针走过的距离均不过超过 N
空间: O(1),存储快慢指针的变量以及存储 head 用于寻找环入口的变量