「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」。
每日刷题第28天 2021.1.24
环形链表
- leetcode原题链接leetcode-cn.com/problems/li…
- 难度:中等
- 方法:快慢指针、数学
题目
- 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 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或者链表中的一个有效索引
解法
解题思路
- 这道题主要考察两点:
- 链表是否有环(也即 141.环形链表 )
- 若链表有环,如何找到入环结点
- 判断链表是否有环
- 使用快慢指针,从头结点出发,
fast指针每次移动两个节点,slow指针每次移动一个节点,若fast 和 slow在途中相遇 ,则说明这个链表有环。 - 这里需要明确两点:
- 若
fast 和 slow相遇,则一定在环中相遇 - 若链表有环,则
fast 和 slow一定会相遇
- 若
- 接下来分析其原因:
fast一定先入环,之后一直在环中循环,因此若相遇,一定是在环中。- 当
slow也入环后,实际上就成了fast追赶slow,而fast每次追上一步,由于环没有终点,因此 fast 必定会追上 slow,两者相遇。
- 若链表有环,如何找到入环结点
- 这里需要一点简单的数学推导,假设头结点到入环结点之间的结点数为
a, 入环结点到fast 与 slow相遇结点之间的结点数为b, 相遇结点再到入环结点之间的结点数为c。则fast走过的结点数lenFast = a + b + n(b + c),slow走过的结点数lenSlow = a + b,而lenFast = 2 * lenSlow,简单的公式推导后可得a = (n - 1) * (b + c) + c。这里用a表示是因为要求的是入环结点,而a正是头结点到入环结点之间的结点数。
回顾
- 从
a = (n - 1) * (b + c) + c中我们可以得到什么呢? - 答案是:若两个指针分别从头结点和相遇结点同时出发,每次都向前移动一步,那么它们会在入环结点处相遇。 其中从相遇结点处出发的指针一共绕环走了
n-1圈,显然n是大于等于1的。 - 注意,之所以在相遇时
slow走过的结点数为a+b而不是a+m(b+c)+b,是因为fast每次追一步,即使在极端情况「slow 入环时 fast 在其前一个结点」下,fast也会先于slow走完一圈前追到slow。
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var detectCycle = function(head) {
// 快慢指针
let fast = head;
let slow = head;
while (fast != null && fast.next != null) {
// 找到首次相遇点
fast = fast.next.next;
slow = slow.next;
if (slow == fast) {
// 第一次相遇就退出
break;
}
}
// 判断是否有环
if(!fast || !fast.next) return null;
// slow还是在相遇点
fast = head;
while (fast != null && slow != null) {
if (fast == slow) break;
fast = fast.next;
slow = slow.next;
}
return fast;
// 慢指针:a + s1
// 快指针:a + s1 + n * (s1 + s2)
// 快指针的速度是慢指针的2倍,那么路径应该也是慢指针的2倍
// 2 * (a + s1) = a + s1 + n * (s1 + s2)
// 2a + 2s1 = a + s1 + ns1 + ns2
// a = ns1 + ns2 - s1
// a = (n - 1)s1 + ns2
// a = (n - 1)(s1 + s2) + s2
// 因此:需要一个指针从开始节点,另一个指针从相遇点开始
};