题目地址:
https://leetcode.com/problems/linked-list-cycle-ii/
链表求环,如果有环,返回环的入口,否则返回null。
经典快慢指针算法。先用快慢指针判断有没有环,见https://blog.csdn.net/qq_46105170/article/details/104013645,然后重新设一个节点cur等于head,接着cur和slow同时出发,每轮循环走一步,直到相遇。则相遇点即为环的入口。代码如下:
public class Solution {
public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null) {
return null;
}
ListNode slow = head, fast = head;
do {
slow = slow.next;
fast = fast.next.next;
} while (fast != null && fast.next != null && slow != fast);
// 说明无环
if (slow != fast) {
return null;
}
ListNode cur = head;
while (cur != slow) {
cur = cur.next;
slow = slow.next;
}
return cur;
}
}
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
时间复杂度 O ( n ) O(n) O(n)。
算法正确性证明:
设链表有环,链表的nodes是这样排列:
v
1
→
v
2
→
.
.
.
→
v
k
−
1
→
v
k
→
v
k
+
1
→
.
.
.
→
v
k
+
s
→
v
k
v_1\rightarrow v_2 \rightarrow ...\rightarrow v_{k-1} \rightarrow v_k\rightarrow v_{k+1}\rightarrow ...\rightarrow v_{k+s}\rightarrow v_k
v1→v2→...→vk−1→vk→vk+1→...→vk+s→vk也就是说从
v
k
v_k
vk到
v
k
+
s
v_{k+s}
vk+s形成一个环,环的边数为
s
+
1
s+1
s+1。我们对于slow和fast各自走了多少步做分析。当slow走到
v
k
v_k
vk时,它走了
k
−
1
k-1
k−1步,而fast走的步数是slow的两倍,所以fast走了
2
(
k
−
1
)
2(k-1)
2(k−1)步,fast所处于的位置的下标是
k
+
(
k
−
1
)
%
(
s
+
1
)
k+(k-1)\ \%\ (s+1)
k+(k−1) % (s+1)。设slow再走
x
x
x步就与fast相遇,那么有同余式:
x
≡
2
x
+
k
−
1
m
o
d
(
s
+
1
)
x\equiv 2x+k-1\mod (s+1)
x≡2x+k−1mod(s+1)所以
x
+
k
−
1
≡
0
m
o
d
(
s
+
1
)
x+k-1\equiv 0\mod (s+1)
x+k−1≡0mod(s+1)也就是说slow再走
k
−
1
k-1
k−1步就到达了
v
k
v_k
vk,而新的节点cur从head出发同时走
k
−
1
k-1
k−1步正好也会走到
v
k
v_k
vk,所以相遇点就是环的入口。