一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
如果一个链表中的某个节点可以连续跟踪其next指针再次到达,则此链表有环,如下图,就是一个入环节点为3,环长为4的环形链表。
1.判断单链表是否有环
给你一个链表的头节点 head ,判断链表中是否有环。如果链表中存在环 ,则返回 true 。 否则,返回 false 。
方案1
在我们遍历链表的时候,将遍历过的节点保存,并且每次都遍历都先与集合中的节点比较,如果遇到相同的,则证明此链表有环。
fun hasCycle(head: ListNode?): Boolean {
val set = HashSet<ListNode>()
var node = head
while (node != null) {
if (set.contains(node)) {
return true
} else {
set.add(node)
node = node.next
}
}
return false
}
此方案只需要遍历一遍,所以时间复杂度为O(N),由于需要使用HashSet记录遍历过的节点,所以空间复杂度为O(N)
方案2
以前玩QQ飞车的时候,经常被第一名超一圈,所以反过来思考,如果两名玩家在一个单链表上赛车,跑得快的在起跑之后如果能还能遇到跑得慢的,那么这个链表有环,如果快的提前到达了终点,说明无环。
fun hasCycle(head: ListNode?): Boolean {
var slow = head
var fast = head
while (fast?.next != null) {
slow = slow?.next
fast = fast.next?.next
if (slow == fast) {
return true
}
}
return false
}
此方案只需要遍历一遍,所以时间复杂度为O(N),由于只使用了两个变量,所以空间复杂度为O(1)
2.求环形链表的入环节点
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
思考
我们假设起点到入环节点长度为S1,入环节点到第一次相遇长度为S2,环的剩余长度为S3,在上一题目中,当两名玩家相遇的时候,慢玩家slow跑得路程为S1+S2,快玩家fast跑得路程为S1+S2+S3+S2,由于快玩家fast的速度是慢玩家slow速度的2倍,所以可以得出快玩家fast跑过的路程也是慢玩家slow的2倍,即2*(S1+S2)=S1+S2+S3+S2,化简之后可以得到S1=S3。那么当两名玩家首次相遇之后,让其中一名玩家回到起点,然后两名玩家一起以相同的速度行驶,再次相遇的时候,相遇点就是入环节点。示意图如下:
fun detectCycle(head: ListNode?): ListNode? {
var slow = head
var fast = head
while (fast?.next != null) {
slow = slow?.next
fast = fast.next?.next
if (slow == fast) {
fast = head
while (slow != fast) {
slow = slow?.next
fast = fast?.next
}
return slow
}
}
return null
}
3.求环形链表的环长
给定一个链表的头节点 head ,返回链表的环长。 如果链表无环,则返回 null。
思考
当慢玩家slow和快玩家fast相遇之后,让慢玩家slow继续前进并记录路程,当慢玩家slow再次回到之前的相遇节点的时候,这段路程就是环长。
fun getCycleLength(head: ListNode?): Int? {
var slow = head
var fast = head
while (fast?.next != null) {
slow = slow?.next
fast = fast.next?.next
if (slow == fast) {
var length = 0
var current = slow
while (current != null) {
current = current.next
length++
if (current == slow) {
return length
}
}
}
}
return null
}