一.题目描述
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
链表中节点的数目范围是 [0, 104]
-105 <= Node.val <= 105
pos 为 -1 或者链表中的一个 有效索引 。
二.题目解析
public boolean hasCycle(ListNode head) {
/*一次遍历,修改遍历过的节点值为Integer.MIN_VALUE,若还将遍历到这个值,证明有环
时间复杂度O(n),空间复杂度O(1),缺点:修改了链表
* */
int min = Integer.MIN_VALUE;
ListNode cur = head;
while (cur != null){
//这个标记再次出现,意味这个结点被访问过,说明链表循环
if(cur.val == min){
return true;
}
//给此正在遍历的节点赋值
cur.val = min;
//遍历下一节点
cur = cur.next;
}
return false;
}
public boolean hasCycle1(ListNode head) {
/*hashset保存遍历过的节点
时间复杂度O(n),空间复杂度O(n)
* */
HashSet<ListNode> listSet = new HashSet<>();
ListNode p = head;
while(true) {
if(p == null) {
return false;
}
if(listSet.contains(p)) {
return true;
}
listSet.add(p);
p = p.next;
}
}
public boolean hasCycle2(ListNode head) {
/*快慢指针法,当快慢指针速度差为1的时候,必会在环内相遇。
当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,
同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会和慢指针相遇
时间复杂度O(n),空间复杂度O(1)
* */
ListNode fast = head,slow = head;
while (fast != null && fast.next != null){
//快慢指针按照不同的速度移动
fast = fast.next.next;
slow = slow.next;
//若相遇则有环,这个判断语句要放在移动语句后面(初始first == slow)
if(fast == slow){
return true;
}
}
return false;
}
public boolean hasCycle3(ListNode head) {
/*先反转再比较,如果有环,那么链表反转之后,
原来的头结点和反转之后的头结点一定是同一个
时间复杂度O(n),空间复杂度O(1)
* */
ListNode reverseHead = reverse(head);
//必须是链表不为空,且非只有一个节点,且reverseHead == head才是有环链表
//如果只有一个节点也满足reverseHead == head,但是却是无环的
if(head != null && head.next != null && reverseHead == head){
return true;
}
return false;
}
public ListNode reverse(ListNode head){
/*反转链表,如果存在环,那么最终cur == null,退出循环,将返回pre即1
1->2->3->4 |------
↑ | -> | ↓
|------| 1->2<-3<-4
* */
ListNode pre = null;
ListNode cur = head,nextCur = null;
while (cur != null){
//记录下个处理的节点
nextCur = cur.next;
//实现反转
cur.next = pre;
//更新反转节点前面的那个节点
pre = cur;
//更新要处理的节点
cur = nextCur;
}
return pre;
}