“Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。”
一、题目描述:
141. 环形链表
给你一个链表的头节点 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, 104]
-105 <= Node.val <= 105
pos 为 -1 或者链表中的一个 有效索引 。
进阶:你能用 O(1)(即,常量)内存解决此问题吗?
二、思路分析:
链表通常最后一项的next指向 null,环形链表的意思就是最后一项next指向之前的某个节点,形成了一个环路。题目是让判断是否为环路,只要循环链表,判断next不是null就为环路,但是怎么知道又走到之前的节点呢。
最简单的侵入式改法,第一次循环加个tag,再回来判断属性是否存在就行,哈哈哈
解法1 侵入式
var hasCycle = function(head) {
while(head){
if(head._tag){
return true
}
head._tag = true
head = head.next
}
return false
};
时间复杂度O(n)
解法2 Set
判断走到之前节点,实质问题在查重,这时候想到 Set 是天然的去重器。用 Set.has() 判断感觉可以搞定,试一下
var hasCycle = function(head) {
const SetBox = new Set()
while(head) { // 如果不是环形链表,走到null就自动退出循环
if(SetBox.has(head){
return true
} else {
SetBox.add(head)
}
head = head.next
}
return false
}
这个执行用时稍微有点慢,时间复杂度O(n)
解法3 快慢指针
这个我没想到,学术名称快慢指针,原理是声明fast,slow两个指针都指向第0项,然后fast每次走两步,slow每次走一步,这样在完整循环一遍链表的时候,fast会追上slow。
怎么理解呢,写个公式就明白了
// step 代表步数, l代表链表长度
1step = 2step + (-l) // -l可以理解为快慢指针的差距。
step = l // 无论l等于几,步数就等于链表长度
或者理解为每走一步,快慢的差距就减少一。
var hasCycle = function(head) {
let slow = head,
fast = head
while(fast && fast.next){ // 走得快,可能不是环形就为null了
slow = slow.next
fast = fast.next.next
if(slow === fast){
return true
}
}
return false
};
ok拉,顾白~