携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
LeetCode 141:环形链表
题目描述
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false
解题思路
思路一: 加标记法
可以遍历链表,给每个已遍历过的节点加上一个标记,如果出现下一个节点已被标记时,则证明链表有环。 实现代码如下:
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function (head) {
while (head !== null) {
if (head.flag) return true;
head.flag = true;
head = head.next;
}
return false;
}
时间复杂度: O(n); 最坏情况下我们需要遍历每个节点一次
空间复杂度: O(n);
思路二: 利用JSON.stringfiy不能序列化含有循环引用的结构
关于JSON.stringfiy用法可以看之前这篇文章掌握JSON.stringify用法,顺便看看json3.js是如何实现的 - 掘金 (juejin.cn)。
利用JSON.stringfiy不能序列化含有循环引用的结构:
var obj = {};
obj.a = obj;
JSON.stringify(obj);
利用这个可判断有对象里有无循环引用:
var hasCycle = function (head) {
try {
JSON.stringify(head);
return false;
} catch (error) {
return true;
}
}
时间复杂度: O(n);
空间复杂度: O(n);
思路三:利用ES6中WeakMap或WeakSet数据结构
这里我们使用weakMap, WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
可以将节点作为键名key进行存储,通过has方法判断节点是否在当前 Map 对象之中,来判断是否有环。
var hasCycle = function (head) {
let map = new WeakMap();
while (head != null) {
if (map.has(head)) {
return true;
}
map.set(head, true);
head = head.next;
}
return false;
}
时间复杂度: O(n); 最坏情况下我们需要遍历每个节点一次
空间复杂度: O(n);
思路四:利用快慢指针
设置快慢两个指针,遍历单链表,快指针一次走两步,慢指针一次走一步,如果存在环,则快慢指针一定会指向同一节点,否则当快指针指向null时,快慢指针都不可能相遇
var hasCycle = function (head) {
if (head === null || head.next === null) {
return false;
}
let slow = head, fast = head;
while(fast.next !== null && fast.next.next !== null) {
fast = fast.next.next;
slow = slow.next;
if (fast === slow) {
return true;
}
}
return false;
}
时间复杂度: O(n);
-
当链表中不存在环时,快指针将先于慢指针到达链表尾部,链表中每个节点至多被访问两次
-
当链表中存在环时,每一轮移动后,快慢指针的距离将减小一。而初始距离为环的长度,因此至多移动 N 轮
空间复杂度: O(1);