这里写目录标题
- 判断链表中是否有环
- 描述
- 代码
- 检测链表中是否存在环
- 链表中存在环
- 想检测链表中是否存在环,而不需要找到环的入口
判断链表中是否有环
题目
描述
判断给定的链表中是否有环。如果有环则返回true,否则返回false。
数据范围:链表长度 0≤n≤10000,链表中任意节点的值满足
∣val∣<=100000
要求:空间复杂度 O(1),时间复杂度 O(n)
输入分为两部分,第一部分为链表,第二部分代表是否有环,然后将组成的head头结点传入到函数里面。-1代表无环,其它的数字代表有环,这些参数解释仅仅是为了方便读者自测调试。实际在编程时读入的是链表的头节点。
例如输入{3,2,0,-4},1时,对应的链表结构如下图所示:
可以看出环的入口结点为从头结点开始的第1个结点(注:头结点为第0个结点),所以输出true。
示例1
输入: {3,2,0,-4},1
返回值: true
说明:第一部分{3,2,0,-4}代表一个链表,第二部分的1表示,-4到位置1(注:头结点为位置0),即-4->2存在一个链接,组成传入的head为一个带环的链表,返回true
示例2
输入:{1},-1
返回值:false
说明: 第一部分{1}代表一个链表,-1代表无环,组成传入head为一个无环的单链表,返回false
示例3
输入:{-1,-7,7,-4,19,6,-9,-5,-2,-5},6
返回值:true
代码
import java.util.*;
/**
* 定义了链表节点的类。
* 每个节点包含一个整数值和一个指向下一个节点的引用。
*/
class ListNode {
int val; // 节点存储的值
ListNode next; // 指向下一个节点的引用
// 构造函数,用于创建一个新的节点,初始化其值和下一个节点的引用
ListNode(int x) {
val = x;
next = null;
}
}
public class Solution {
/**
* 检测链表中是否存在环。
* @param head 链表的头节点。
* @return 如果链表中存在环,返回true;否则返回false。
*/
public boolean hasCycle(ListNode head) {
// 如果头节点为空,则链表不存在环
if (head == null) {
return false;
}
// 初始化两个指针fast和slow,它们都指向头节点
ListNode fast = head;
ListNode slow = head;
// 使用循环进行检测,直到fast和slow相遇或者fast到达链表末尾
do {
// 如果fast的下一个节点或者下下个节点为空,说明链表没有环
if (fast.next == null || fast.next.next == null) {
return false;
}
// 移动fast指针,每次移动两步
fast = fast.next.next;
// 移动slow指针,每次移动一步
slow = slow.next;
} while (fast != slow); // 如果fast和slow相遇,说明存在环
// 如果循环结束,fast没有和slow相遇,说明链表没有环
return true;
}
}
检测链表中是否存在环
使用的是快慢指针(也称为龟兔赛跑算法)来检测链表中是否存在环。以下是快慢指针相遇的逻辑详细解释:
-
初始化:我们有两个指针
fast
和slow
,它们都从链表的头节点开始。 -
移动指针:
fast
指针每次移动两步(即fast = fast.next.next
)。slow
指针每次移动一步(即slow = slow.next
)。
-
相遇条件:
- 如果链表中存在环,那么
fast
指针和slow
指针最终会相遇。这是因为fast
指针移动的速度是slow
指针的两倍,所以它们会在环内相遇。 - 如果链表中没有环,
fast
指针会先到达链表的末尾(即fast
或fast.next
为null
),此时fast
和slow
不会相遇。
- 如果链表中存在环,那么
-
检测逻辑:
- 在每次循环中,我们首先检查
fast
指针的下一个节点或下下个节点是否为null
。如果为null
,则说明链表没有环,直接返回false
。 - 如果
fast
指针的下一个节点或下下个节点不为null
,则继续移动fast
和slow
指针。 - 当
fast
和slow
指针相遇时,说明链表中存在环,返回true
。
- 在每次循环中,我们首先检查
-
结束条件:
- 如果
fast
指针在移动过程中到达了链表的末尾(即fast
或fast.next
为null
),则说明链表中没有环,返回false
。
- 如果
通过这种方式,快慢指针算法能够有效地检测出链表中是否存在环,并且时间复杂度为O(n),空间复杂度为O(1)。
链表中存在环
如果链表中存在环,并且fast
和slow
指针相遇,接下来的处理取决于你想要完成的具体任务。以下是一些常见的处理方式:
-
检测环的长度:
- 一旦
fast
和slow
相遇,你可以继续移动其中一个指针(通常是slow
),每次移动一步,直到再次遇到环的起点。这样你可以计算出环的长度。
- 一旦
-
找到环的入口:
- 你可以在
fast
和slow
相遇后,将其中一个指针(通常是fast
)重新指向头节点,然后同时移动fast
和slow
,每次移动一步。当它们再次相遇时,相遇点就是环的入口。
- 你可以在
-
删除环:
- 如果你的目标是删除环,可以在找到环的入口后,通过调整指针来删除环。例如,你可以将环的最后一个节点的
next
指针设置为null
。
- 如果你的目标是删除环,可以在找到环的入口后,通过调整指针来删除环。例如,你可以将环的最后一个节点的
-
重新初始化指针:
- 如果你只是需要检测环的存在,那么在
fast
和slow
相遇后,你可以直接返回true
。
- 如果你只是需要检测环的存在,那么在
以下是一些示例代码,展示如何在fast
和slow
相遇后找到环的入口:
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null) {
return false;
}
ListNode fast = head;
ListNode slow = head;
while (fast != slow) {
if (fast == null || fast.next == null) {
return false;
}
fast = fast.next.next;
slow = slow.next;
}
// fast 和 slow 相遇,重新初始化 fast 指向头节点
fast = head;
// 当 fast 和 slow 再次相遇时,相遇点就是环的入口
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
// 返回 true 表示链表中有环
return true;
}
}
在这个示例中,一旦fast
和slow
相遇,我们将fast
重新指向头节点,然后同时移动fast
和slow
,直到它们再次相遇。这时的相遇点就是环的入口。这种方法可以确保你找到环的入口,而不仅仅是检测环的存在。
想检测链表中是否存在环,而不需要找到环的入口
如果你只想检测链表中是否存在环,而不需要找到环的入口,你可以简化代码,只保留检测环存在的逻辑。以下是修改后的代码示例:
public class Solution {
/**
* 检测链表中是否存在环。
* @param head 链表的头节点。
* @return 如果链表中存在环,返回true;否则返回false。
*/
public boolean hasCycle(ListNode head) {
if (head == null) {
return false;
}
// 初始化两个指针fast和slow,它们都指向头节点
ListNode fast = head;
ListNode slow = head;
// 使用循环进行检测,直到fast和slow相遇或者fast到达链表末尾
while (fast != null && fast.next != null) {
// 移动fast指针,每次移动两步
fast = fast.next.next;
// 移动slow指针,每次移动一步
slow = slow.next;
// 如果fast和slow相遇,说明存在环
if (fast == slow) {
return true;
}
}
// 如果fast到达链表末尾,说明没有环
return false;
}
}
在这个修改后的代码中,我们只保留了循环,直到fast
指针到达链表的末尾或fast
和slow
相遇。如果fast
和slow
相遇,函数立即返回true
,表示链表中存在环。如果fast
到达链表的末尾(即fast
或fast.next
为null
),则循环结束,函数返回false
,表示链表中没有环。
这种方法的时间复杂度是O(n),空间复杂度是O(1),非常高效。