题目
题解
leetcode给出的方法参数只有ListNode,一开始我一直搞不懂这个pos要怎么接收??
思路一:使用哈希表,让每一个节点做key,value记录每一个节点出现的次数。如果有一个节点出现两次说明有环。
这里使用HashMap或者HasnSet都可以。
/**
* 每次把哈希表中没有的结点添加到哈希表中。
* 如果是环,那么在尾结点的下一个结点肯定在哈希表中,此时就可以说明有环。
* 如果不是环,那么在尾结点的下一个结点就是null,此时结束循环,说明不是环
* @param head
* @return
*/
public boolean hasCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
while(head != null) {
if(set.contains(head)) {
// 说明存在环
return true;
} else {
set.add(head);
}
head = head.next;
}
return false;
}
复杂度:
- 时间复杂度:遍历N次,所以为O(N)。
- 空间复杂度:使用一个哈希表,最多存储N个节点,所以为O(N)。
思路二:使用双指针,一个是慢指针slow,每次移动一个节点,另一个是快指针fast,每次移动两个节点。
对于快慢指针,需要解决一个问题,比如一个单链表如果从左到右使用快慢指针遍历,必定是快指针先遇到null。那么是不是:fast.next.next == null ?其实没有必要,只需要:fast.next == null 就行。但是仅仅这样是错误的,快慢指针还得考虑节点个数的奇偶问题。对于奇数来说这样没问题,但是对于偶数就有问题了,会出现空指针异常,可以画画图,快指针最后会跳到null去,你可能会说,跳到null去不正好结束吗?是,不过在还得进行一轮的判断是否结束才算结束,问题就是出现在这最后一次判断这,空指针调用了next。所以还得判断:fast == null。
总结(掌握):对于快慢指针,需要解决节点个数的奇偶问题,因为快指针最先到达结尾,所以快指针需要两个条件来判断是否结束:如果当前快指针为空或者快指针的下一个节点为空,表示结束。
- 假设是无环的情况下,那么就是遇到null,必定是快指针先遇到。
- 假设是有环的情况下,那么就是不会遇到null,但是快慢指针就一直循环下去吗?看下图:
通过上图,发现如果链表是环,那么快慢指针一直循环就一定能相遇。(我不会证明。。)
代码:
/**
*
* @param head
* @return
*/
public boolean hasCycle2(ListNode head) {
ListNode fast = head;
ListNode slow = head;
// 如果不是环,结点个数影响快指针跳的位置,所以需要写一个处理奇偶情况。此题跟求链表的中间结点那道的条件一样。不过不需要去考虑中间结点有2个的问题
while(fast != null && fast.next != null) {
// 必须先走,才判断是否为环
// 慢指针走一步
slow = slow.next;
// 快指针走两步
fast = fast.next.next;
// 如果遇到环,则快指针和慢指针一定会在环相遇
if(fast == slow) {
return true;
}
}
return false;
}
因为这道题比较特殊,还可以这样:
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.next == null) {
return false;
}
fast = fast.next.next;
slow = slow.next;
}
return true;
}
复杂度:
- 时间复杂度:O(N)
- 空间复杂度:就用两个指针,可以当作O(1)