「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」
题目
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意: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 <= 105pos为-1或者链表中的一个 有效索引 。
**进阶:**你能用 O(1)(即,常量)内存解决此问题吗?
解题
解题一:判断重复节点(HashSet)
思路
从头节点开始,依次遍历单链表中的每一个节点,用 HashSet 存放曾经遍历过的 Node 节点,每遍历一个节点判断 HashSet 是否有相同的值,如果发现 HashSet 中存在与之相同的的节点,则说明链表有环,返回 True,如果发现 HashSet 中不存在与之相同的的节点,就把这个新节点存入 HashSet 中,之后进入下一个节点,继续重复刚才的操作。遍历完成后还没有返回值,就返回 -1
代码
/**
* 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) {
// 当链表为空,或者只有一个节点时,不可能构成环形链表
if (head == null || head.next == null) {
return false;
}
// 存放已经遍历过的节点,用于判断是否有相同节点
HashSet<ListNode> resultSet = new HashSet<>();
// 遍历链表
while (head != null) {
if (resultSet.contains(head)) {
// 当 resultSet 存在相同节点时,代表形成环形链表,返回 true
return true;
}
// 当resultSet 不存在相同节点时,将遍历的节点存入 resultSet,继续遍历链表
resultSet.add(head);
head = head.next;
}
// 链表已经遍历完,没有相同节点,代表没有构成环形链表
return false;
}
}
总结
特点
- 使用了 HashSet 作为额外的缓存
注意点
- 判断是否有相同的节点使用 Node 判断,而不是 Node.val 判断
- 使用 HashSet 比 HashMap 能更节省空间
性能分析
- 时间复杂度:O(n)
- 空间复杂度:O(n)
- 执行用时:4 ms,在所有 Java 提交中击败了 22.35% 的用户
- 内存消耗:38.8 MB,在所有 Java 提交中击败了 97.67% 的用户
解题二:双指针法(快慢指针)
思路
将链表有环问题改变为追及问题
首先创建两个指针 p1 和 p2,让它们同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针 p1 每次向后移动 1 个节点,让指针 p2 每次向后移动 2 个节点,然后比较两个指针指向的节点是否相同。如果相同,则可以判断出链表有环,如果不同,则继续下一次循环,直到 p2 指针指向空
代码
/**
* 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) {
// 当链表为空,或者只有一个节点时,不可能构成环形链表
if (head == null || head.next == null) {
return false;
}
// p1 指针指向头节点的下一个节点
ListNode p1 = head.next;
ListNode p2 = null;
if(p1.next !=null){
// 当 p1 的下一个节点不为 null 时,设置 p2 指针指向 p1 指针的下一个节点
p2 = p1.next;
}
while(p2 !=null && p2.next !=null && p2.next.next !=null){
// 满足 p1 走 1 步,p2 走 2 步,开始遍历链表
if(p1 == p2){
// 当 p1 == p2 时,代表构成循环链表
return true;
}else{
// 当 p1 != p2 时,继续循环链表,p1 走 1 步,p2 走两步
p1 = p1.next;
p2 = p2.next.next;
}
}
// 链表已经遍历完,没有相同节点,代表没有构成环形链表
return false;
}
}
动态图片
总结
- 时间复杂度:O(n)
- 空间复杂度:O(1)
- 执行用时:0 ms,在所有 Java 提交中击败了 100% 的用户
- 内存消耗:39.3 MB,在所有 Java 提交中击败了 72.74% 的用户