快慢指针查找入环点
成环一定相遇证明
假设一个环上有 N 个节点,有快指针 Fast
与慢指针 Slow
在环上任意两点开始出发,只要 N 是有限的,总有 Fast
超过 Slow
的时候,那么在 Fast
超过 Slow
之前有两种情况:
-
Fast 在 Slow 之前一个,此时 Fast 在 m+1 的位置,Slow 在 m+2 的位置,下一次运动,Fast 与 Slow 都会运动到 m+3 的位置,Fast 与 Slow 相遇。
-
Fast 在 Slow 之前两个,此时 Fast 在 m 的位置,Slow 在 m+2 的位置,下一次运动,Fast 在 m+2 的位置,Slow 在 m+3 的位置,则会回到情况1,Fast 与 Slow 依然相遇。
因此可以得出:Fast 第一次追上 Slow 的时候就会与 Slow 相遇,不会超过 Slow;
可使用该方法证明一个链表上是否有环:
func hasCycle(head *ListNode) bool {
if head == nil || head.Next == nil {
return false
}
fastNode, lowNode := head, head
for fastNode != nil && fastNode.Next != nil {
lowNode = lowNode.Next
fastNode = fastNode.Next.Next
if lowNode == fastNode {
return true
}
}
return false
}
入环节点查找
Slow入环时有两种情况:
-
假设 Slow 在入环的时候,就与 Fast 相遇了,此时 Fast 已经在环上跑了 n(n>=1) 圈了,此时假设入环之前有 a 个节点,环上有 b 个节点,已知 Fast 运动的长度一定是 Slow 的 2 倍,可知:
2×a = a + n×b
则可得出a = n×b
,此时从相遇点出发的指针,与从链表头部出发的指针同时同速运动,则一定会在入环的节点相遇; -
假设 Slow 在入环的时候,没有与 Fast 相遇,入环前有 a 个节点,因为此时 Fast 在 Slow 之前,因此 Slow 在入环后没有运行完一圈就会与 Fast 相遇,假设此时 Slow 运行了 b 个节点,距离入环点 c 个节点。此时 Fast 已经在环上跑了 n(n>=1) 圈外加 b 个节点了,此时有
2×(a+b) = a + n×(b+c) +b
转换后有a = (n-1)×(b+c) + c
。由于 c 为剩下的节点,则刚好 a 为剩下 c 加上整数个环长度,与从链表头部出发的指针同时同速运动,则一定会在入环的节点相遇;
以上两种情况下,均会在入环点相遇,因此可证明可使用该方法在查找入环点:
func detectCycle(head *ListNode) *ListNode {
if head == nil {
return nil
}
intersect := getIntersect(head)
if intersect == nil {
return nil
}
ptr1 := head
ptr2 := intersect
for ptr1 != ptr2 {
ptr1 = ptr1.Next
ptr2 = ptr2.Next
}
return ptr1
}
func getIntersect(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return nil
}
fastNode, lowNode := head, head
for fastNode != nil && fastNode.Next != nil {
lowNode = lowNode.Next
fastNode = fastNode.Next.Next
if lowNode == fastNode {
return fastNode
}
}
return nil
}