「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战」。
题目
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例1
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例2
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
思路
按照题意,如果 head
链表中存在环,则在遍历链表时,总会在某个时机,遇到重复的节点
那么最常见的解法就是,遍历所有的节点,每次遍历到一个新节点的时候,判断当前节点是否被访问过;可以用 Set
结构存下所有已访问的节点,每次遍历到下一个节点的时候,如果这个节点能在 Set
结构中被找到,则说明 head
中存在环
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function(head) {
const nodeSet = new Set();
while (head) {
if(nodeSet.has(head)) {
return true;
} else {
nodeSet.add(head);
head = head.next;
}
}
return false;
};
上面的解法,时间复杂度为 O(n)
,空间复杂度为 O(n)
,因为有可能需要完整的遍历链表,同时将所有的节点存入 Set
中,才能判断环的存在与否;
为了优化空间复杂度,还可以使用两个快慢指针,来处理这个问题;借图说明:
将一个指针初始化为 head
位置,定义为慢指针;一个初始化在 head.next
位置,定义为快指针;慢指针每次移动一步,快指针每次移动两步;那么显然,如果 head
中存在环,则快指针必然先进入环,然后慢指针也会进入环,由于快指针比慢指针走得快,必然会在某个时刻,快指针在环上会追上慢指针,也称为 相遇
,这个时候 慢指针 == 快指针
;如果不存在环,则快指针会优先到达链表的尾部,即 快指针.next == null
;
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function(head) {
if(!head || !head.next) return false;
let slowPoint = head;
let fastPoint = head.next;
while (slowPoint != fastPoint) {
if(fastPoint == null || fastPoint.next == null) {
return false
}
// 快指针走两步,慢指针走一步
fastPoint = fastPoint.next.next;
slowPoint = slowPoint.next;
}
return true;
};
小结
上述的快慢指针算法,也称为 Floyd 判圈算法
;相对于第一种使用哈希表的解法,空间复杂度优化到了 O(1)
,但是时间复杂度更长了,因为哈希表的最长时间为 O(n)
,而快慢指针算法,在环存在的情况下,在慢指针进入环之后,依然还要进行快指针追上慢指针的步骤
算法的优化,两种常见的手段,空间换时间
,例如 dfs回溯
时,常会用到的 记忆化
优化;两一种就是相对的 时间换空间
,例如本题的 快慢指针
解法;
LeetCode 👉 HOT 100 👉 环形链表 - 简单题 ✅
合集:LeetCode 👉 HOT 100,有空就会更新,大家多多支持,点个赞👍
如果大家有好的解法,或者发现本文理解不对的地方,欢迎留言评论 😄