大家好!我是曾续缘😛
今天是《LeetCode 热题 100》系列
发车第 26 天
链表第 5 题
❤️点赞 👍 收藏 ⭐再看,养成习惯
给定一个链表的头节点
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 <= 105
pos
的值为-1
或者链表中的一个有效索引进阶:你是否可以使用
难度:💖💖O(1)
空间解决此题?
解题方法
环的存在性检测:
快慢指针法的基本思想是利用速度差来检测环。假设链表中没有环,快指针会先于慢指针到达链表的末尾。
如果链表中存在环,由于快指针的速度是慢指针的两倍,快指针会在环内追上慢指针。
一旦快慢指针相遇,说明链表中存在环。
找到环的起始节点:
当快慢指针第一次相遇时,假设从头节点到环的起始节点的距离为 d
,从环的起始节点到相遇点的距离为 x
,环的长度为 L
。
设慢指针从头节点到第一次相遇点时,在环中绕了n
圈,经过的总距离为 d + x + nL
,
快指针在环中绕了m
圈,经过的总距离为 d + x + mL
。
根据速度关系有d + x + mL = 2(d + x + nL)
,即d = (m-2n)L - x
,
化简得d = kL - x
,其中 k
为整数,等于(m-2n)
。
d = kL - x
,这意味着从头节点到环的起始节点的距离(d
)等于从第一次相遇点(-x
,该点距离环入口有一定距离,因此需要少走x距离)绕环一周或多周再回到相遇点的距离(kL
)。
当快指针再次以慢指针的速度从头节点出发走了距离d
时,快指针到达了环入口处,
慢指针走了距离kL - x
,等价于在环里走了(k+1)
圈再回退x
距离,即同样到了环的入口处。
过程如下:
- 初始化两个指针
slow
和fast
都指向链表的头部head
。 - 在一个循环中移动这两个指针,直到它们相遇或者快指针到达链表的末尾:
- 慢指针每次移动一步:
slow = slow.next
- 快指针每次移动两步:
fast = fast.next.next
- 慢指针每次移动一步:
- 如果快指针或快指针的下一个节点变为
null
,说明链表没有环,函数返回null
。 - 如果快慢指针在环内相遇,即
fast == slow
,则进行下一步。 - 将快指针重新定位到链表头部:
fast = head
。 - 以相同的速度移动两个指针,直到它们再次相遇,这个相遇点就是环的入口:
fast = fast.next
slow = slow.next
- 当
fast == slow
时,返回当前节点,即为环的起始节点。
Code
查看代码
public class Solution {
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
ListNode slow = head;
ListNode fast = head;
// 第一阶段:检测环的存在
do {
if (fast == null || fast.next == null) {
return null; // 无环
}
fast = fast.next.next;
slow = slow.next;
} while (fast != slow);
// 第二阶段:找到环的起始节点
fast = head;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast; // 返回环的起始节点
}
}