环形链表问题
1. 问题描述
在一个单向链表中,某个节点的 next
指针指向了之前的某个节点,从而形成了一个环。
通常有两个问题需要解决:
- 如何判断链表是否有环?
- 如果链表有环,如何找到环的起始节点?
2. 解题思路
1. 判断链表是否有环(Floyd 判圈算法 / 快慢指针法)
- 使用 快指针
fast
和 慢指针slow
:slow
每次走一步fast
每次走两步
- 如果
fast
追上slow
(即fast == slow
),说明链表中有环。 - 如果
fast
或fast.next
为空,则链表无环。
2. 找到环的起始节点(Floyd 定理)
- 当
fast
和slow
相遇时,slow
已经走了x
步,而fast
走了2x
步。 - 假设 环入口节点 距离链表头
L
,环的长度为C
,则fast
和slow
相遇的位置M
距离环入口E
还有d
步。 - Floyd 定理推导出:
- 从链表头(head)出发一个新指针
ptr
,从相遇点M
出发一个新指针slow
,二者每次走一步,最终会在环的起始节点E
相遇。
- 从链表头(head)出发一个新指针
3. Java 代码实现
class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
this.next = null;
}
}
public class LinkedListCycle {
// 方法1:判断链表是否有环
public 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;
}
// 方法2:找到环的起始节点
public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null) return null;
ListNode slow = head;
ListNode fast = head;
ListNode entry = head; // 用于寻找环的起始节点
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) { // 快慢指针相遇
while (entry != slow) { // 另一个指针从头开始,每次走一步
entry = entry.next;
slow = slow.next;
}
return entry; // 返回环的入口
}
}
return null; // 无环
}
// 测试代码
public static void main(String[] args) {
// 构造环形链表:1 -> 2 -> 3 -> 4 -> 5 -> 3 (成环)
ListNode head = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(5);
head.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node3; // 形成环,指向 node3
LinkedListCycle solution = new LinkedListCycle();
System.out.println("链表是否有环:" + solution.hasCycle(head));
ListNode cycleStart = solution.detectCycle(head);
System.out.println("环的起始节点:" + (cycleStart != null ? cycleStart.val : "无环"));
}
}
4. 代码解析
1. hasCycle(ListNode head)
- 功能:判断链表是否存在环。
- 实现:
- 使用快慢指针,
slow
走一步,fast
走两步。 - 如果
slow == fast
,说明链表有环。 - 如果
fast
或fast.next
为空,则链表无环。
- 使用快慢指针,
- 时间复杂度:O(N),N 为链表节点数。
- 空间复杂度:O(1),只使用了两个指针。
2. detectCycle(ListNode head)
- 功能:找到环的起始节点。
- 实现:
- 先使用
hasCycle()
方法判断是否有环。 - 若有环,则从
head
处定义新指针entry
,与slow
继续移动,每次走一步。 - 当
entry == slow
,即为环的起点。
- 先使用
- 时间复杂度:O(N),N 为链表节点数。
- 空间复杂度:O(1),只使用了额外指针。
5. 测试结果
运行程序,输出:
链表是否有环:true
环的起始节点:3
说明:
hasCycle
正确检测到链表存在环。detectCycle
正确返回了环的入口节点3
。
6. 总结
- 快慢指针法 是检测链表是否有环的高效方法,时间复杂度 O(N),空间复杂度 O(1)。
- Floyd 定理 可帮助快速找到环的起始节点。
- 适用于一般的单向链表,并可扩展到
N
个节点的情况。
这样,我们完整实现了 判断链表是否有环 和 寻找环的起始节点 的 Java 代码! 🚀