一、题目描述
给你一个链表的头节点 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, 10^4]
-10^5 <= Node.val <= 10^5
pos
为-1
或者链表中的一个 有效索引 。
二、解题思路
这个问题可以使用快慢指针(Floyd’s cycle-finding algorithm)的算法来解决。我们可以设置两个指针,一个快指针(每次移动两步)和一个慢指针(每次移动一步)。如果链表中存在环,那么快慢指针最终会在环内相遇;如果链表中没有环,快指针将会先到达链表的末尾。
算法步骤:
- 初始化快慢两个指针,都指向链表的头节点。
- 在快指针没有到达链表末尾的情况下,进行循环: a. 快指针向前移动两步。 b. 慢指针向前移动一步。 c. 检查快慢指针是否相遇。
- 如果快慢指针相遇,则链表存在环,返回true。
- 如果快指针到达链表末尾,则链表无环,返回false。
三、具体代码
public class Solution {
public boolean hasCycle(ListNode head) {
// 快慢指针初始都指向头节点
ListNode slow = head;
ListNode fast = head;
// 当快指针没有到达链表末尾时进行循环
while (fast != null && fast.next != null) {
slow = slow.next; // 慢指针前进一步
fast = fast.next.next; // 快指针前进两步
// 如果快慢指针相遇,则存在环
if (slow == fast) {
return true;
}
}
// 如果快指针到达链表末尾,则没有环
return false;
}
}
四、时间复杂度和空间复杂度
1. 时间复杂度
- 假设链表的长度为
n
,如果有环,快指针和慢指针最终会在环内相遇。 - 在没有遇到慢指针的情况下,快指针每次移动两步,慢指针每次移动一步,因此,每轮循环中,快慢指针之间的距离会减少1。
- 当快指针进入环时,它需要走
n
步才能追上慢指针(因为慢指针此时已经在环内了)。 - 因此,最多会有
n
轮循环,每轮循环中快慢指针各移动一次,所以时间复杂度是O(n)
。
2. 空间复杂度
- 快慢指针算法只使用了固定数量的额外空间(两个指针),与链表的长度无关。
- 因此,空间复杂度是
O(1)
,即常数空间复杂度。
综上所述,快慢指针算法用于检测链表中的环的时间复杂度是 O(n)
,空间复杂度是 O(1)
。
五、总结知识点
-
链表(Linked List):链表是一种常见的数据结构,它由一系列节点组成,每个节点包含数据和一个或多个指向其他节点的引用(或指针)。
-
单链表(Singly Linked List):每个节点只包含一个指向下一个节点的引用的链表。
-
节点(Node):链表中的基本单元,通常包含数据和指向下一个节点的指针。
-
循环(Loop):在编程中,循环用于重复执行一组指令,直到满足某个条件为止。本代码中使用了一个
while
循环来遍历链表。 -
指针(Pointer):在链表的操作中,指针用于跟踪当前处理的节点。本代码中使用了两个指针,一个慢指针和一个快指针。
-
快慢指针法(Floyd’s cycle-finding algorithm):这是一种检测链表中是否存在环的方法,通过设置两个速度不同的指针,一个快一个慢,如果在链表中存在环,那么这两个指针最终会在环内相遇。
-
条件语句(Conditional Statement):用于根据条件执行不同的代码路径。本代码中使用了
if
语句来检查快慢指针是否相遇。 -
空值检查(Null Check):在处理链表时,需要检查节点是否为
null
,以避免空指针异常。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。