总结
两种解决方法:
- 哈希表
- 快慢指针
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
- 链表中节点的数目范围是
[0, 104]
-105 <= Node.val <= 105
pos
为-1
或者链表中的一个 有效索引 。
进阶:你能用 O(1)
(即,常量)内存解决此问题吗?
方法一:哈希表
思路与算法
逻辑上:遍历所有节点,每次遍历到一个节点,判断该节点是否已经被访问过。
具体:使用哈希表存储所有已经访问过的节点。每到达一个节点,如果该节点已经存在哈希表中,说明该表已经是环形链表,否则将该节点加入哈希表。循环往复,直至遍历整个链表。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_set<ListNode*> seen;
while(head != nullptr){
if(seen.count(head)){
return true;
}
seen.insert(head);
head = head->next;
}
return false;
}
};
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),其中n是链表中的节点数,最坏情况下需要遍历每个节点一次。
- 空间复杂度: O ( n ) O(n) O(n),其中n是链表中的节点数。主要是哈希表的开销,最坏情况下需要将每个节点插入到哈希表中一次。
方法二:快慢指针
思路与算法
「Floyd判圈」(龟兔赛跑算法)
定义两个指针,一快一慢。
慢指针每次只移动一步,而快指针每次移动两步。
初始化时,慢指针在位置 head
,而快指针在位置 head.next
。
如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head == nullptr || head->next == nullptr){
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
while(slow != fast){
if(fast == nullptr || fast->next == nullptr){
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
复杂度分析
- 时间复杂度:
O
(
n
)
O(n)
O(n),其中n是链表中的节点数。
- 当链表中不存在环时,快指针将先于慢指针到达链表尾部,链表中每个节点至多被访问两次。
- 当链表中存在环时,每一轮移动后,快慢指针的距离将减小一。而初始距离为环的长度,因此至多移动n轮。
- 空间复杂度: O ( 1 ) O(1) O(1),只使用了两个指针的额外空间。