一、题目描述
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
二、思路解析
下文中,我们将环的起点设置为node。
将问题抽象后,将解决的问题变成两个问题:
- 如何判定是否有环?
- 如何求起点?
2.1如何判定有环:
这里的判定不是List中的值的判定,而是List本身的判定,判定List一直往下走会不会再走一遍自己走过的路。这里很明显会用到双指针,而这个双指针该如何使用呢?
随想录中使用到的方法是两个指针,fast一次走2步,slow每次走一步。将会得到两个指针必在环内相遇这个结论。原因是:我们从slow这个指针进入环开始分析,从slow进入环开始,slow和fast的相对位置是一格一格得相对缩小的,所以是必能相遇的。而且是slow的运动不会超过这个环,两个指针就会相遇了。
根据下图的图示:当两指针相遇时
- slow:运动距离为 x+y
- fast: 运动距离为 x+n(y+z)+y
- 根据两指针的步长关系:2x+2y = x+n(y+z)+y
化简可得:x=(n-1)y+nz=(n-1)(y+z)+z.
2.2 如何求起点
仔细观察上面的公式,左边表达的意思是从head到环起点的距离,右边表达的意思是从node到环起点的距离再加上转了n-1圈的距离。上面我们可以求的node的位置,那么我们可以再设置两个指针,一个从head开始,一个从node开始,当两指针相遇时,就是起点的位置。
三、代码实现
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head, slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
ListNode index1 = head, index2 = fast;
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}
其中有个小问题就是 while (fast != null && fast.next != null) 这个判断条件,我一开始写的是while (fast.next.next != null && fast.next != null).其问题在于没有判断一开始fast就是null的情况。