一、题目
网址:https://leetcode-cn.com/problems/linked-list-cycle/
题目:141. 环形链表
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 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 或者链表中的一个有效索引
二、解题
1.分析题目
目的:判断链表是否有环
输入:输入一个数组(例子中pos只是为了告知你该链表是否有环,不为输入)
输出:输出一个boolean类型值true或false用于反正是否是环
官方测试用例:
输入
[3,2,0,-4] 1
输出
true
输入
[1,2] 0
输出
true
输入
[1] -1
输出
false
提示内容:引入一个pos表示有效索引
注意:链表中节点的数目范围是 [0, 1000],-100000<= Node.val <=100000
实际测试用例:
输入
[] -1
输出
false
输入
[1,2] -1
输出
false
2.思考+思路
2.1自己思考1~~~暴力破解
如果不是环则在最后node.next = null,而是环的话则在某一个点的多个next后可以再回到这个点。
2.1.1思路1~~~一个指针无限next找null
设置一个指针,无限next,设置循环最多10010个,如果没返回false则一定为环,暴力结束。
2.1.2思路2~~~双指针(PASS)
设置两个指针,第一个指针从第一个node开始,遍历所有node。第二个指针在每一个node遍历的时候从node+1开始直到最后,如果存在null直接结束,不存在null也无法结束。(该方法无意义,舍弃)
2.2自己思考2~~~哈希表应用
若是环则代表某一个Node出现两次,此时我们可以存储所有Node,而存储的话使用哈希表来存储Node,如果Node重复直接出结果有环。
思路1~~~定义哈希表存储所有Node
通过Set<ListNode>定义哈希表,然后存储Node。
2.3题解思路1~~~双指针
参考链接:
https://leetcode-cn.com/problems/linked-list-cycle/solution/huan-xing-lian-biao-by-leetcode/
这个方法自己在其他题解里面看到说是快慢指针。
通过使用具有不同速度 的快、慢两个指针遍历链表,空间复杂度可以被降低至 O(1)。慢指针每次移动一步,而快指针每次移动两步。
如果列表中不存在环,最终快指针将会最先到达尾部,此时我们可以返回 false。
这个方法相当于是对暴力破解的一个优化,首先暴力破解分为两部分:若不是环直接结束,若是环则循环10010次。而双指针里面快的指针在第一遍遍历的时候需要判断是否返回null即判断是否是环。然后若是环的情况,通过设置一个双指针来进行优化次数。
注:具体的解释还是可以去看参考链接的解释。
3.实现
3.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;
}
//定义一个点表示当前节点
ListNode ans = head;
//节点最多1000次,定义一个1010的循环,如果1010次循环还没返回false则一定为环
for(int i=0;i<10010;i++)
{
//如果next为空则表示无下个节点了,表示不为环返回false
if(ans.next == null)
return false;
//获取下一个节点
ans = ans.next;
}
return true;
}
}
执行结果:通过
执行用时:1 ms, 在所有 Java 提交中击败了27.02%的用户
内存消耗:38.8 MB, 在所有 Java 提交中击败了83.09%的用户
分析:简简单单的暴力破解,性能十分糟糕。主要是每一个环都要10010次,所以耗时巨多。
3.2哈希表应用
/**
* 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) {
// 定义一个哈希表
Set<ListNode> ans = new HashSet<>();
// 定义一个Node存储值
ListNode ans_Node = head;
while (ans_Node != null) {
// contains()方法用于检查是否有任何键映射到给定值元素(val_ele)中。
if (ans.contains(ans_Node)) {
return true;
} else {
// 将ans_Node添加到ans中
ans.add(ans_Node);
}
ans_Node = ans_Node.next;
}
return false;
}
}
执行结果:通过
执行用时:5 ms, 在所有 Java 提交中击败了16.82%的用户
内存消耗:39.1 MB, 在所有 Java 提交中击败了30.94%的用户
分析,该方法用时反而增加了,初步怀疑是每一个ans_Node的存储都消耗了一定的时间,所以时间增加。内存消耗无变化。
三、其他答案
1.双指针
public class Solution {
public boolean hasCycle(ListNode head) {
// 如果链表为空,或头节点无下个节点则表示无环
if (head == null || head.next == null) {
return false;
}
//定义快慢两个节点
ListNode slow = head;
ListNode fast = head.next;
//当快的和慢的相等则表示追上了,则肯定有环
while (slow != fast) {
//对不是环的情况进行判断
if (fast == null || fast.next == null) {
return false;
}
//设置快慢两个速度
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
执行结果:通过
执行用时:0 ms,在所有Java提交中击败了100.00%的用户
内存消耗:39.2 MB,在所有Java提交中击败了25.04%的用户
分析:这个结果好优啊,通过减少环的时间大大的提高了速度。
四、总结
本题整体难度较低,通过Java方法解题一般内存都在39M上下浮动。
建议学习链接如下:
环形链表:https://leetcode-cn.com/problems/linked-list-cycle/solution/huan-xing-lian-biao-by-leetcode/
练习本题需要掌握两个知识点——双指针和哈希表
双指针主要是定义两个指针来进行一些距离限定或者两节点相等的操作
双指主要针用于如下场景:获取倒数第k个元素,获取中间位置的元素,判断链表是否存在环,判断环的长度等和长度与位置有关的问题。
哈希表主要是需要掌握可以使用哈希表来存储指针的内容。
五、Python
个人在学习Python,所以用Python实现了下上述方法。
1.暴力破解
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head or not head.next: #节点不存在或下一个节点不存在
return False
for i in range(0,10010):
if not head.next:
return False
head = head.next
return True
执行结果:通过
执行用时:80 ms,在所有Python3提交中击败了13.09%的用户
内存消耗:16.5 MB,在所有Python3提交中击败了14.39%的用户
2.集合
class Solution:
def hasCycle(self, head: ListNode) -> bool:
a = set() # 定义集合
while head:
# 如果head已存在于a
if head in a:
return True
# 当head不在a里面,则添加head
a.add(head)
head = head.next
return False
执行结果:通过
执行用时:76 ms,在所有Python3提交中击败了17.23%的用户
内存消耗:16.7 MB,在所有Python3提交中击败了9.39%的用户
3.双指针
class Solution:
def hasCycle(self, head: ListNode) -> bool:
# 节点不存在或下一个节点不存在
if not head or not head.next:
return False
# 定义快慢两个节点
slow=head
fast=head.next
while slow!=fast:
# 如果快的为null则表示无环
if fast is None or fast.next is None:
return False
slow=slow.next
fast=fast.next.next
return True
执行结果:通过
执行用时:72 ms,在所有Python3提交中击败了24.37%的用户
内存消耗:16.2 MB,在所有Python3提交中击败了85.73%的用户