题目
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。可参考下图:
解题思路有两种:哈希表和快慢指针。
假设已有链表节点实现:
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
哈希表
利用哈希表中key值唯一的特性,将每个节点的地址存入哈希表中。由于链表中的每个节点唯一,当节点中地址已存在哈希表中,则证明链表有环。
实现代码如下:
public class Solution {
public boolean hasCycle(ListNode head) {
HashSet<ListNode> nodeSet = new HashSet<>();
while (head != null){
// 判断哈希表中是否存在该节点地址
if (nodeSet.contains(head)){
return true;
}else {
// 将节点地址添加到哈希表中
nodeSet.add(head);
}
head = head.next;
}
return false;
}
}
快慢指针
快慢指针的思路在于维护两个指针,一开始两个指针起点相同,快指针每次都比慢指针多移动一个节点(按照链表的节点顺序移动),如果链表有环,则快指针最终会追上慢指针。相当于在环形跑道上跑步的两名运动员,跑得快的运动员在一段时间后会与跑得慢的运动员再次相遇。如果链表无环,则快指针不会与慢指针相遇。
实现代码如下:
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null){
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while(slow != fast){
// 判断快指针是否到底链表尾部
if(fast == null || fast.next == null){
return false;
}
slow = slow.next;
fast = fast.next.next;
}
// 快慢指针相遇
return true;
}
}
复杂度分析
哈希表
(1)时间复杂度:HashSet添加元素的时间复杂度为O(1),对于n个元素的链表,时间复杂度为O(n)。
(2)空间复杂度:空间复杂度取决于添加到HashSet中的元素数量,对于n个元素的链表,空间复杂度为O(n)。
快慢指针
(1)时间复杂度:
- 链表无环:快指针到达链表尾部,时间复杂度取决于链表长度,为O(n)。
- 链表有环:假设链表分为两部分:非环部分和环形部分。非环部分长度为N,环形部分长度为K。在最坏的情况下,即慢指针走完非环部分进入环形部分,此时快指针刚好走到慢指针的下一个节点,可参考下图。由于快指针每次比慢指针移动一个节点,故快指针遇到慢指针所需时间复杂度为O(N+K),即O(n)。
(2)空间复杂度:只使用可快慢指针两个节点,故空间复杂度为O(1);