环形链表1--判断链表是否有环
题目解析
给你一个链表的头节点
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)
(即,常量)内存解决此问题吗?
思路
思路1:哈希表法,遍历链表,把节点存入set,如果有环,那么环遍历一遍后,最终肯定会存在于set中。
思路2:弗洛伊德龟兔赛跑法,
假想「乌龟」和「兔子」在链表上移动,「兔子」跑得快,「乌龟」跑得慢。当「乌龟」和「兔子」从链表上的同一个节点开始移动时,如果该链表中没有环,那么「兔子」将一直处于「乌龟」的前方;如果该链表中有环,那么「兔子」会先于「乌龟」进入环,并且一直在环内移动。等到「乌龟」进入环时,由于「兔子」的速度快,它一定会在某个时刻与乌龟相遇,即套了「乌龟」若干圈。
我们可以根据上述思路来解决本题。具体地,我们定义两个指针,一快一慢。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。
代码
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> seen = new HashSet<ListNode>();
while (head != null) {
if (!seen.add(head)) {
return true;
}
head = head.next;
}
return false;
}
}
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
//head指向null或者链表只有一个元素,直接返回false
if(head==null||head.next==null){
return false;
}
//初始化两个快慢指针,慢的一次移动一下,快的一次移动两下,如果有环,肯定会相等
ListNode slow=head;
ListNode fast = head.next.next;
while(slow!=fast&&fast!=null&&slow!=null){
slow=current.next;
//需要确保fast.next不能为null
if(fast.next!=null){
fast = fast.next.next;
}else{
fast=null;
}
}
return fast!=null;
}
}
笑话
环形链表2--找到入环点
题目解析
给定一个链表的头节点
head
,返回链表开始入环的第一个节点。 如果链表无环,则返回null
。如果链表中有某个节点,可以通过连续跟踪
next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果pos
是-1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。提示:
- 链表中节点的数目范围在范围
[0, 104]
内-105 <= Node.val <= 105
pos
的值为-1
或者链表中的一个有效索引进阶:你是否可以使用
O(1)
空间解决此题?
思路
思路一
同找环的方法哈希表法,遍历链表,把节点存入set,如果有环,那么环遍历一遍后,最终肯定会存在于set中,第一个在set中的就是链表开始入环的第一个节点
思路二
仍然使用弗洛伊德龟兔赛跑法
设链表中环外部分的长度为 a。slow 指针进入环后,又走了 b 的距离与 fast 相遇。此时,fast 指针已经走完了环的 n 圈,因此它走过的总距离为 a+n(b+c)+b=a+(n+1)b+nc。
有了 a=c+(n−1)(b+c) 的等量关系,我们会发现:从相遇点到入环点的距离c加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离a。
因此,当发现 slow 与 fast 相遇时,我们再使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow 每次向后移动一个位置。最终,它们会在入环点相遇。
上边的解释意思是:当fast和slow在环内相遇时,此时再新增一个指针ptr指向链表头,然后一次移动一步,slow也从相遇点一次移动一步在环内移动,根据公式a=c+(n−1)(b+c),最终一定是在入环点相遇,此时可能是slow在环内转了一圈+c的距离,此时a=c
也可能是转了多圈,总之会在入环点相遇
代码
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode current = head;
Set<ListNode> set = new HashSet<>();
while(current!=null){
if(set.contains(current)){
return current;
}else{
set.add(current);
current=current.next;
}
}
return null;
}
}
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
//排除头结点为空场景
if(head==null){
return null;
}
//定义slow,fast都指向头指针
ListNode slow=head;
ListNode fast=head;
//fast=null说明没环之间返回null
while(fast!=null){
//fast.next!=null说明可以继续进行循环下去
if(fast.next!=null){
//慢指针一次移动一下,快指针一次移动两下
slow=slow.next;
fast=fast.next.next;
}else{
//fast.next=null,说明没环
return null;
}
//当有环时
if(fast==slow){
//定义新指针指向头
ListNode ptr=head;
//没相遇
while(ptr!=slow){
//ptr,slow都移动一步
ptr=ptr.next;
slow=slow.next;
}
//相遇点就是入环点
return ptr;
}
}
return null;
}
}