给定一个链表,判断链表中是否有环。
思路
1.两个指针指向单链表的首元结点,一个快一个慢,向后遍历单链表,如果有环路,移动的快的指针一定会追上慢的结点(多走一圈)。
2.利用哈希表,每次把结点存入表,如果遇到重复的结点,则为环链表。
测试用例
- 空链表
- 非环链
- 首尾相交环链
- 中间结点相交环链
尽量在写代码之间考虑好所有边界条件,这样才能写出健壮的代码。
博主代码如下:
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode p = head;
ListNode q = head;
if(head==null)
return false;
while(p!=null&&q!=null){//p指针一次走一步
for(int i=1;i<=2;i++){//q指针一次走两步
if(q.next!=null){
q=q.next;
if(p.equals(q)) return true;//如果相遇,则为环链
}else{
return false;
}
}
p=p.next;
}
return false;
}
}
领扣答案:
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;
}
博主思路与领扣思路比较:
博主的循环条件是:如果没有到单链表链尾
标准答案思路:如果快跑者与慢跑着没有相遇
思路出发点不同,结果相同
复杂度分析
-
时间复杂度:O(n)O(n), 让我们将 nn 设为链表中结点的总数。为了分析时间复杂度,我们分别考虑下面两种情况。
-
链表中不存在环:
快指针将会首先到达尾部,其时间取决于列表的长度,也就是 O(n)O(n)。 -
链表中存在环:
我们将慢指针的移动过程划分为两个阶段:非环部分与环形部分:-
慢指针在走完非环部分阶段后将进入环形部分:此时,快指针已经进入环中 \text{迭代次数} = \text{非环部分长度} = N迭代次数=非环部分长度=N
-
两个指针都在环形区域中:考虑两个在环形赛道上的运动员 - 快跑者每次移动两步而慢跑者每次只移动一步。其速度的差值为1,因此需要经过 \dfrac{\text{二者之间距离}}{\text{速度差值}}速度差值二者之间距离 次循环后,快跑者可以追上慢跑者。这个距离几乎就是 "\text{环形部分长度 K}环形部分长度 K" 且速度差值为 1,我们得出这样的结论 \text{迭代次数} = \text{近似于}迭代次数=近似于 "\text{环形部分长度 K}环形部分长度 K".
-
因此,在最糟糕的情形下,时间复杂度为 O(N+K)O(N+K),也就是 O(n)O(n)。
-
-
空间复杂度:O(1)O(1), 我们只使用了慢指针和快指针两个结点,所以空间复杂度为 O(1)O(1)。
思路2(领扣标准答案)
public boolean hasCycle(ListNode head) {
Set<ListNode> nodesSeen = new HashSet<>();
while (head != null) {
if (nodesSeen.contains(head)) {
return true;
} else {
nodesSeen.add(head);
}
head = head.next;
}
return false;
}
复杂度分析
-
时间复杂度:O(n)O(n), 对于含有 nn 个元素的链表,我们访问每个元素最多一次。添加一个结点到哈希表中只需要花费 O(1)O(1) 的时间。
-
空间复杂度:O(n)O(n), 空间取决于添加到哈希表中的元素数目,最多可以添加 nn 个元素。