题目描述:
难度:简单
给你一个链表的头节点 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
或者链表中的一个 有效索引 。
解题准备:
1.链表:链表是一个线性结构,其优点是在内存空间足够的情况下,可以任意增加节点,并且不会受到连续内存块的要求,因此非常灵活。缺点:对于结点node,node只能知道node之后的结点,无法得到node之前的结点【对于单链表】
2.环:对于单链表,如果某个结点node,node的某个子孙结点i,i指向node,就称链表存在环。很容易看出一些性质:node结点一般有两个前驱结点【如果不是头结点】;如果尝试遍历整个链表,一定会陷入死循环;在遍历整个链表时,一定会重复出现某个序列;【不过,这些性质对于解题帮助不大】
3.了解本题可能涉及的操作:既然要判断链表中是否有环,大概率涉及查找、比较,查找找到可能是环的结点,比较判断是否有环;总结:查找、比较。
4.链表的查找:由于不确定链表长度,我们一般用while循环,从头节点,一直遍历到尾节点tail,结束条件利用tail一定指向null做判断。
5.链表的比较:判断链表是否有环,需要使用结点node与结点i比较,判断二者的内存地址是否相同。
解题思路:
不从技术的角度(即链表三板斧:快慢指针、栈、哑节点)出发,我们最容易想到的解决思路,大概就是:
第一次思路:
1.遍历一次链表,对每个节点访问记录+1;
2.不断遍历,只要出现某个节点的访问记录>1,就说明链表有环。
当然,不从技术性角度出发,这个思路是没有问题的,就算链表的入环点是头节点,第二次遍历到头节点时,其访问记录也一定为2;
回归到实现:
1.访问记录这个概念,怎么实现?
1st.如果采用数组,数组应该多大?
2nd.由于链表的val域不使用,我们可以在第遍历一次链表时,将val域数据=1,第二次遍历时,+1【当然,不推荐修改原数据】
11.问题是:如果val域原数据就是1怎么办?如果选用遍历一次,将所有数据置为x,那么又要考虑,如果链表有环,则不可能遍历结束!
3rd.怎么判断之前是否经历过这个结点?我们已经剔除val域的想法,那么,是否需要记录先前经历过的所有结点?并且,在每次遍历时,将所有结点与某节点比较判断,查看是否有重复?
11.问题是:算法时间复杂度非常高,如果用ArrayList,设添加为O(n)【添加了n个结点】,如果在第n+1个结点得到重复的数,那么,对于第1个、第2个……到第n个结点,一共需要判断N!次;又因为涉及外部循环n次,所以算法复杂度1为O(n!*n);
虽然题目对时间不限制,不过写起来也很麻烦,对于访问记录,几乎不可实现。
如果没法做到访问记录,从哪一条路走呢?
其实我们何必判断访问次数呢?只要遇到相同的结点,不就说明存在环吗?
那么,干脆直接用ArrayList存储经历过的所有结点,只要新结点与ArrayList中的结点相同,就说明存在环。
第二次思路:
1.遍历链表,将结点加入ArrayList。
2.判断新的结点,是否与ArrayList中数据相同,相同则为有环,返回true
3.直到结点为null,说明链表不存在环,返回false。
从技术性角度出发,这个也是可以实现的,不过时间复杂度与第一次思路相差无几,需要优化。
假设本题有环,遍历到n+1时找到环结点。
那么,遍历是n+1次,对ArrayList中数据判断,是(n+1)!次
由于题目约束比较少,遍历的次数基本没法省略,又因为判断需要的时间太多,可以从此处优化。
我们记得,在HashSet中,判断元素是否存在,效率是O(1)。
干脆用HashSet优化。
那么,思路就是:
1.遍历链表,将结点加入HashSet
2.判断新来的结点,在HashSet中是否存在?存在则有环返回true;
3.一直到结点为null,说明链表无环,返回false;
这样优化,时间复杂度大约在O(n),效率还是比较高的。
另外,还有一种快慢指针的方法,具体的理论和实现在我的另一篇博客快慢指针:如何判断单链表是否有环-CSDN博客中详细说明
其时间复杂度也是O(n),不过空间复杂度为O(1),在此不赘叙。
解题难点分析:
本题朴素思路行不通的原因,是判断元素是否重复的算法比较难设计。
如果要我说,就应该对HashMap、HashSet再多理解理解,其妙用确实很厉害。
代码:
public class Solution {
public boolean hasCycle(ListNode head) {
HashSet<ListNode> temp = new HashSet<>();
// HashSet判断是否重复
while(head!=null){
if(temp.contains(head)){
return true;
}
temp.add(head);
head = head.next;
}
return false;
}
}
以上内容即我想分享的关于力扣热题20的一些知识。
我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。