题目:142. 环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
进阶:你是否可以使用 O(1) 空间解决此题?
思路
简单的解法就是使用HashSet,利用元素不可重复的特性找到入环节点,我们这里不做讲解,底下附有实现代码
另一种做法就是利用快慢指针找环入口
我们详细说明一下快慢指针的解法
设置两个指针fast和slow,其中fast一次跨两步,slow一次一步,现在有两种情况:
- 链表无环,fast与slow永远不会相遇,当fast为空或者fast.next为空时,返回null结束
- 链表有环(如上图),那么fast与slow相遇必定是在环内,现在假设,环外的长度为a,fast与slow相遇的点为b,从点b到环入点的距离为c
- 因为fast一次走两步,所以,当两个指针相遇时,fast走过的距离为 a + n(b + c) + b,(未知数n是fast走过的圈数)
-
slow一次走一步,此时slow走过的距离为 a + b ,注意,这里存在迷惑点,可能有人会觉得为什么slow会在走不完一圈的情况下与fast相遇(参考自 @astrophel 这位老哥的评论,很清晰)
- 这里我们可以这样想,第一步,快指针fast先进入环
- 第二步,慢指针slow进入环,此时fast与slow相距 x,x一定是大于等于0的,假设环的长度为n,那么fast需要追赶slow的长度就为n-x
- 这里可以看成fast的速度是2步每秒,slow的速度是1步每秒,相当于fast每秒钟离slow的距离就会拉近一步,那么fast要追赶到slow就需要n-x秒,在这n-x秒中,slow走了n-x步,而 x >= 0,slow走的路程就小于等于n,
- 即慢指针走不完一圈,就会与快指针相遇
- 接着,因为fast一次走两步,而slow一次走一步,可以得知,fast走过的路程是slow 的两倍,也就是 a + n(b + c) + b = 2(a + b),化简可以得出 a = (n - 1)(b + c) + c
- a 也就是从头指针到环入口的距离,等于(n - 1)圈环的距离再加上c,而c就是快慢指针相遇的点距环入口的距离
- 所以我们可以将fast重新设置指向head,slow位置不变,slow和fast同时每轮向前走一步
- 当fast走过的路程为 a 的同时,slow 走过的路程为 c,此时两者再次相遇,并且同时指向环的入口
- 返回fast或slow结束
代码
//1. 使用HashSet解决问题
public class Solution {
public ListNode detectCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
while (head != null) {
if (!set.add(head)) {
return head;
}
head = head.next;
}
return null;
}
}
//2. 使用快慢指针解决问题
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (true) {
if (fast == null || fast.next == null)
return null;
fast = fast.next.next;
slow = slow.next;
if (fast == slow)
break;
}
fast = head;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
}