1、描述
给定一个链表,返回链表开始入环的第一个节点,如果链表无环,则返回null。
为了表示给定链表中的环,我们使用整数pos 来表示链表尾连接到链表中的位置(索引从0开始)。如果pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表
例1:输入:head = [3, 2, 0, -4] pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个结点
例2:输入:head = [1, 2] pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个结点
2、算法
1)哈希表
思想:我们分配一个 Set 去保存所有的列表节点。我们逐一遍历列表,检查当前节点是否出现过,如果节点已经出现过,那么一定形成了环且它是环的入口。否则如果有其他点是环的入口,我们应该先访问到其他节点而不是这个节点。其他情况,没有成环则直接返回 nil
时间复杂度:O(n)
func detectCycle(_ head:ListNode?)->ListNode?{
/*
哈希表
*/
var head = head
var nodeSeen : [Int:ListNode?] = [:]
while head != nil {
if nodeSeen[(head?.val)!] != nil {
return head
}else{
nodeSeen[(head?.val)!] = head
}
head = head?.next
}
return nil
}
2)双指针
思想:双指针
算法被划分成两个不同的 阶段 :
在第一阶段,找出列表中是否有环,如果没有环,可以直接返回 null 并退出。
否则,用 相遇节点 来找到环的入口。
时间复杂度:O(n)
func detectCycle2(_ head:ListNode?)->ListNode?{
/*
双指针
*/
if head == nil || head?.next == nil {
return nil
}
//如果这里有一个环,快/慢指针就会有一个相遇节点,反之,返回nil
let intersect = getIntersect(head)
if intersect == nil {
return nil
}
//用两个相同速度的指针,一个从链表头开始,一个从相遇节点开始,当它们指向同一个节点时,这个结点就是环的入口
var ptr1 = head
var ptr2 = intersect
while ptr1?.val != ptr2?.val {
ptr1 = ptr1?.next
ptr2 = ptr2?.next
}
return ptr1
}
private class func getIntersect(_ head:ListNode?)->ListNode?{
var tortoise = head
var hare = head
//快指针会遍历一圈环去遇见慢指针
//如果没有环,则返回空
while hare != nil && hare?.next != nil {
tortoise = tortoise?.next
hare = hare?.next?.next
if tortoise?.val == hare?.val{
return tortoise
}
}
return nil
}