目录
题目
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
思路
题目有两个要求:1.判断是否有环 2.如果有,找到环的入口
首先来看第一个要求
定义一个快指针一个慢指针,让快指针一次走两个结点,慢指针一次走一个结点,如果有环的话快指针会先进入,然后满指针也进入,快指针走的快,一直在环里,所以一定会和慢指针相遇,所以这个可以用来判断是否有环。
Q:为什么快指针要走两个节点?
A:快指针走两个,慢指针走一个,所以快指针相当于慢指针只走了一个,相当于快指针一格一格逼近慢指针,假如快指针一下走三个的话很有可能就直接跳过慢指针了。
然后我们来看第二个要求
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。
那么相遇时: slow指针走过的节点数为: x + y
, fast指针走过的节点数:x + y + n (y + z)
,n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。
Q:为什么慢指针这里不用再加一个k(z+y)?也就是说为什么慢指针一定在第一圈的时候就被快指针追上去了?
A:把环展开
相当于在这个中间慢指针一定会被快指针相遇
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y
,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
特殊情况:当n=1的时候,我们得到x=z
所以我们在相遇的点处设置一个l1,在头的部分设置l2 ,让他们移动相同距离,最终他们相遇的位置就是环的入口
从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
Q:那n大于1是什么情况?
A:相当于快指针在环中绕了n-1圈以后然后才和l2相遇,看图即可理解
代码
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){//因为快指针要一次跳两格,所以要确定它和它的下一个都不能为null
fast=fast.next.next;//fast跳两格
slow=slow.next;//slow跳一格
if(slow==fast){//相遇了,判断有环了
ListNode l1=fast;
ListNode l2=head;
while(l1!=l2){//他俩还没相遇,继续遍历
l1=l1.next;
l2=l2.next;
}
return l1;//找到返回
}
}
return null;//没有环
}
}