每日算法随笔:环形链表
题解:环形链表
在这道题目中,我们需要判断一个链表是否存在环。环的定义是链表的某个节点可以通过连续跟踪 next
指针回到自身。如果存在这样的环,那么就返回 true
,否则返回 false
。
方法一:使用哈希集合 (HashSet)
思路:
- 遍历链表,使用一个哈希集合 (
HashSet
) 存储每个访问过的节点。 - 每遍历一个节点时,检查该节点是否已经存在于集合中。如果存在,说明链表中存在环;如果不存在,则将该节点加入集合中。
- 如果遍历完整个链表都没有发现重复节点,说明链表中不存在环。
代码实现:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> set = new HashSet<ListNode>();
ListNode curr = head;
while (curr != null) {
// 如果当前节点已经在集合中,说明有环
if (!set.add(curr)) {
return true;
}
curr = curr.next; // 继续遍历下一个节点
}
return false; // 如果遍历到链表末尾没有发现环
}
}
复杂度分析:
- 时间复杂度:O(n),其中
n
是链表的节点数。我们每个节点只遍历一次。 - 空间复杂度:O(n),需要额外的哈希集合来存储每个访问的节点。
过程解析:
- 初始化一个空的
HashSet
。 - 从头节点开始遍历链表,每遇到一个节点,检查它是否在
HashSet
中。如果在,说明有环,返回true
;否则,将该节点加入集合,继续遍历下一个节点。 - 如果遍历到了链表的末尾(即
curr == null
),说明链表没有环,返回false
。
方法二:快慢指针 (Floyd 判圈算法)
思路:
- 使用两个指针:一个快指针 (
fast
),一个慢指针 (slow
)。 - 快指针每次走两步,慢指针每次走一步。
- 如果链表中没有环,快指针会在遍历完链表时到达
null
。 - 如果链表中有环,快指针会最终追上慢指针,两者会相遇。
代码实现:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) return false;
ListNode slow = head; // 慢指针
ListNode fast = head.next; // 快指针
// 快慢指针相遇说明有环
while (fast != slow) {
if (fast == null || fast.next == null) return false; // 快指针提前到达终点,说明没有环
fast = fast.next.next; // 快指针走两步
slow = slow.next; // 慢指针走一步
}
return true; // 两个指针相遇,说明有环
}
}
复杂度分析:
- 时间复杂度:O(n),其中
n
是链表的节点数。最坏情况下,快指针和慢指针遍历链表中的每个节点。 - 空间复杂度:O(1),我们只用了常数级别的额外空间。
过程解析:
- 快指针
fast
每次走两步,慢指针slow
每次走一步。 - 如果链表中有环,快指针最终会追上慢指针,这时返回
true
。 - 如果快指针在遍历过程中到达了
null
,则链表没有环,返回false
。
方法比较:
- 哈希集合法使用了额外的存储空间,但思路简单易懂,适合初学者。
- 快慢指针法虽然稍微复杂一点,但它不需要额外的空间,能够以 O(1) 的空间复杂度解决问题,在面试中常被要求使用这种方法。
这道题目考察了链表的基本操作以及快慢指针的应用,通过分析链表的结构,可以更好地掌握链表与指针的使用。