package main
import "fmt"
/*
题目: 判断一个单向链表中是否存在环
最优解思路:1. 使用两个指针,起点都从链表的头结点开始,第一个指针每一步走一个节点,第二个指针每一步走两个节点,
如果存在环两个指针一定会相遇的。这类似于小学中学的追击问题:在一个环形跑道上两个运动员同一地点出发一个运动员速度快另一个速度慢,两个运动员一定会相遇。
*/
// 假设链表节点如下
type Node struct {
num int
next *Node
}
// 查看给定的链表是否有环
func findRing(head *Node) bool {
// p1 p2 指针
p1 := head
p2 := head
for {
// p1 每循环一次移动一个节点
if p1 != nil {
p1 = p1.next
} else {
return false
}
// p2 每循环一次移动两个节点
if p2 != nil && p2.next != nil {
p2 = p2.next.next
} else {
return false
}
// 判断p1 p2是否相等,如果相等则说明存在环
if p1 == p2 {
return true
}
}
}
/*
问题扩展: 1. 求出环的长度。 2. 求出入环点
思路:1. 环长 = p2前进的距离 - p1 前进的距离 (首次相遇)
2. 根据相遇问题解析的到,首次相遇点到入环点的距离(运动方向距离)与起点到入环点的距离相等,由此我们需要求得相遇点,
然后两个指针相同速度一个从head出发,一个从入环点出发,两个指针会在入环点相遇
*/
// 1. 求出环长度
func getRingLength(head *Node) int {
// p1 p2 指针,每一次分别走一步和两步,从head出发求出首次相遇各走了多少个节点
p1 := head
p2 := head
sum1 := 0
sum2 := 0
for {
p1 = p1.next
p2 = p2.next.next
sum1 += 1
sum2 += 2
if p1 == p2 {
return sum2 - sum1
}
}
}
// 2. 求出入环点
func getEntrance(head *Node) *Node {
p1 := head
p2 := head
p3 := head
for {
p1 = p1.next
p2 = p2.next.next
if p1 == p2 {
for {
// 找到首次相遇点
// 然后p1从首次相遇点出发,p3从head出发,速度都为1,他们会在入环点相遇
p3 = p3.next
p1 = p1.next
if p1 == p3 {
// 找到入环点
return p1
}
}
}
}
}
// 构造数据来测试
func main() {
// 1. 构造一条存在环的链表
node01 := &Node{num: 1}
node02 := &Node{num: 2}
node03 := &Node{num: 3}
node04 := &Node{num: 4}
node05 := &Node{num: 5}
node06 := &Node{num: 6}
node07 := &Node{num: 7}
node08 := &Node{num: 8}
node09 := &Node{num: 9}
node10 := &Node{num: 10}
node01.next = node02
node02.next = node03
node03.next = node04
node04.next = node05
node05.next = node06
node06.next = node07
node07.next = node08
node08.next = node09
node09.next = node10
// 形成环
node10.next = node05
/*
形成如下环
1-2-3-4-5-6- 7
| |
10-9-8
*/
// 判断是否存在环
boolen := findRing(node01)
fmt.Println(boolen)
// 求环长度
ringLength := getRingLength(node01)
fmt.Println(ringLength)
// 求出入环点
entranceNode := getEntrance(node01)
fmt.Println(entranceNode)
}
