快慢指针 (fast and slow pointer)
判断链表是否有环, 找环入口
public class CycleLinkedListTest {
public static class ListNode {
String val;
ListNode next;
public ListNode(String val) {
this.val = val;
}
}
public static boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
public static ListNode detectCycleEntry(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
}
public static void main(String[] args) {
// 构建链表 A→B→C→D→B
ListNode A = new ListNode("A");
ListNode B = new ListNode("B");
ListNode C = new ListNode("C");
ListNode D = new ListNode("D");
A.next = B;
B.next = C;
C.next = D;
// 形成环
D.next = B;
// 判断是否有环
boolean hasCycle = hasCycle(A);
System.out.println("是否有环:" + hasCycle); // true
// 找环入口
ListNode entry = detectCycleEntry(A);
if (entry != null) {
System.out.println("环的入口节点是:" + entry.val); // B
}
}
}
数学证明(极简版,一看就懂)
假设:
- 头节点到环入口:
a步 - 环入口到相遇点:
b步 - 相遇点回到环入口:
c步
快慢指针第一次相遇时:
- 慢指针走了:
a + b - 快指针走了:
a + b + n*(b+c)(多绕了 n 圈)
因为快指针速度 = 2 倍慢指针:
2(a + b) = a + b + n(b+c)
化简:a = n(b+c) - b
a = (n-1)(b+c) + c
结论:
头节点到环入口的距离 a = 相遇点绕环回到入口的距离 c
所以必须:
slow回到起点fast从相遇点继续一步一步走- 两者同时前进,再次相遇的地方就是环入口