文章目录
一、题目简介
给定一个单链表的头结点 head,判断这个链表中是否存在环。如果链表中存在某个节点,通过不断地 next 指针可以再次到达该节点,就说明链表中存在环。
👉 返回结果:
- 存在环 ⇒
true - 无环 ⇒
false
二、问题抽象化与示意图
我们可以把链表抽象成一个节点序列,每个节点指向下一个节点。如果在某处某个节点的 next 指针指向了前面的节点,就形成了"环"。
下面用一个简化的示意来展示这种结构:
Head
Node 2
Node 3
Node 4
🔁 可以看到:节点 D 的指针指向了节点 B,使得链表循环回去形成了环。
三、解法一:哈希表法(检测重复节点)
原理说明
- 用一个哈希表(如
HashSet)记录访问过的节点。 - 每访问一个节点,就检查它是否已经存在于哈希表中。
- 如果存在,则说明链表中出现了环。
- 若遍历到
null说明到达链表尾部,没有环。
解法流程图
Yes
Yes
No
No
Start
Set current = head
current != null ?
current in visitedSet ?
Return true
Add current to visitedSet
Move current to current.next
Return false
时间与空间复杂度
- 时间复杂度:
O(n)(每个节点最多访问一次) - 空间复杂度:
O(n)(哈希表存储节点引用)
Java 代码实现
java
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> visited = new HashSet<>();
ListNode current = head;
while (current != null) {
if (visited.contains(current)) {
return true;
}
visited.add(current);
current = current.next;
}
return false;
}
}
四、解法二:快慢指针法(Floyd 判圈算法)
原理说明
又称为"龟兔赛跑算法"(Tortoise and Hare Algorithm):
- 设置两个指针:慢指针
slow每次走一步;快指针fast每次走两步。 - 如果链表没有环,
fast会先到达末尾(null)。 - 如果链表有环,
slow与fast最终会在环内某处相遇。
时序示意图
节点序列 fast指针 slow指针 节点序列 fast指针 slow指针 初始化:slow = head, fast = head 否则继续移动,直到 fast == null 或相遇 fast前进两步 slow前进一步 如果两者指向同一节点 ⇒ 存在环
解法流程图
Yes
Yes
No
No
Start
slow = head, fast = head
fast != null && fast.next != null ?
slow = slow.next
fast = fast.next.next
slow == fast ?
Return true
Return false
时间与空间复杂度
- 时间复杂度:
O(n)(快慢指针在环中最多相遇一次) - 空间复杂度:
O(1)(只使用常数额外空间)
Java 代码实现
java
public class Solution {
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; // fast 到达末尾,无环
}
}
五、总结
| 方法 | 原理 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|---|
| 哈希表法 | 使用集合检测重复节点 | O(n) | O(n) | 简单易懂但消耗额外空间 |
| 快慢指针法 | 双指针运动相遇判环 | O(n) | O(1) | 更高效,推荐使用 |