Начало цикла в связанном списке
Условие задачи
Дана голова односвязного списка, содержащего цикл. Напишите функцию, которая находит начальный узел цикла.
Решение
Если известна длина цикла, его начало можно найти с помощью двух указателей:
- Сначала обнаружим цикл быстрым и медленным указателями. После их встречи обойдём цикл ещё раз и посчитаем его длину
k. - Установим два новых указателя на голову списка и передвинем второй на
kузлов вперёд. - Будем перемещать оба указателя на один узел за шаг. Когда они встретятся, второй указатель успеет пройти внутри цикла ровно один дополнительный круг, поэтому точка встречи будет началом цикла.
Код
Вот как будет выглядеть наш алгоритм:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package main
import "fmt"
type ListNode struct {
Value int
Next *ListNode
}
func findCycleStart(head *ListNode) *ListNode {
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
if slow == fast {
cycleLength := calculateCycleLength(slow)
return findStart(head, cycleLength)
}
}
return nil
}
func calculateCycleLength(node *ListNode) int {
current := node.Next
cycleLength := 1
for current != node {
current = current.Next
cycleLength++
}
return cycleLength
}
func findStart(head *ListNode, cycleLength int) *ListNode {
pointer1, pointer2 := head, head
for i := 0; i < cycleLength; i++ {
pointer2 = pointer2.Next
}
for pointer1 != pointer2 {
pointer1 = pointer1.Next
pointer2 = pointer2.Next
}
return pointer1
}
func main() {
head := &ListNode{Value: 1}
head.Next = &ListNode{Value: 2}
head.Next.Next = &ListNode{Value: 3}
head.Next.Next.Next = &ListNode{Value: 4}
head.Next.Next.Next.Next = &ListNode{Value: 5}
head.Next.Next.Next.Next.Next = &ListNode{Value: 6}
tail := head.Next.Next.Next.Next.Next
tail.Next = head.Next.Next
fmt.Println("Начало цикла:", findCycleStart(head).Value)
tail.Next = head.Next.Next.Next
fmt.Println("Начало цикла:", findCycleStart(head).Value)
tail.Next = head
fmt.Println("Начало цикла:", findCycleStart(head).Value)
}
Вывод:
1
2
3
Начало цикла: 3
Начало цикла: 4
Начало цикла: 1
Временная сложность
Обнаружение цикла, вычисление его длины и поиск начального узла требуют линейного времени. Общая временная сложность равна \(O(N)\), где \(N\) — количество узлов в списке.
Пространственная сложность
Алгоритм использует постоянный объём дополнительной памяти, поэтому пространственная сложность равна \(O(1)\).