Инвертирование участка связанного списка
Условие задачи
Дана голова связанного списка и две позиции p и q. Инвертируйте участок списка от позиции p до позиции q.
Решение
Задача следует паттерну инвертирования связанного списка на месте. Мы можем использовать подход, похожий на рассмотренный в задаче об инвертировании всего списка. Нужно выполнить следующие шаги:
- Пропустить первые
p-1узлов, чтобы дойти до узла в позицииp. - Запомнить узел в позиции
p-1, чтобы позднее соединить его с инвертированным участком списка. - Инвертировать узлы от позиции
pдо позицииqтем же способом, который использовался для инвертирования всего списка. - Соединить узлы в позициях
p-1иq+1с инвертированным участком.
Код
Вот как будет выглядеть наш алгоритм:
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
71
72
73
74
75
76
77
package main
import "fmt"
type ListNode struct {
Value int
Next *ListNode
}
func reverseSubList(head *ListNode, p, q int) *ListNode {
if head == nil || p == q {
return head
}
current := head
var previous *ListNode
for i := 0; current != nil && i < p-1; i++ {
previous = current
current = current.Next
}
if current == nil {
return head
}
lastNodeOfFirstPart := previous
lastNodeOfSubList := current
for i := 0; current != nil && i < q-p+1; i++ {
next := current.Next
current.Next = previous
previous = current
current = next
}
if lastNodeOfFirstPart != nil {
lastNodeOfFirstPart.Next = previous
} else {
head = previous
}
lastNodeOfSubList.Next = current
return head
}
func newList(values ...int) *ListNode {
dummy := &ListNode{}
current := dummy
for _, value := range values {
current.Next = &ListNode{Value: value}
current = current.Next
}
return dummy.Next
}
func printList(head *ListNode) {
first := true
for current := head; current != nil; current = current.Next {
if !first {
fmt.Print(" ")
}
fmt.Print(current.Value)
first = false
}
fmt.Println()
}
func main() {
head := newList(1, 2, 3, 4, 5)
head = reverseSubList(head, 2, 4)
fmt.Print("Узлы инвертированного связанного списка: ")
printList(head)
}
Вывод:
1
Узлы инвертированного связанного списка: 1 4 3 2 5
Временная сложность
Временная сложность алгоритма равна \(O(N)\), где \(N\) — общее количество узлов в связанном списке.
Пространственная сложность
Мы использовали только постоянный объём памяти, поэтому пространственная сложность алгоритма равна \(O(1)\).
Вариации задачи
Задача 1: инвертируйте первые k элементов заданного связанного списка.
Решение: эту задачу легко свести к исходной. Чтобы инвертировать первые k узлов списка, нужно передать p=1 и q=k.
Задача 2: дан связанный список из n узлов. Инвертируйте его в зависимости от размера следующим образом:
- Если
nчётно, инвертируйте список группами поn/2узлов. - Если
nнечётно, оставьте средний узел на месте, инвертируйте первыеn/2узлов и последниеn/2узлов.
Решение: если n чётно, можно выполнить следующие шаги:
- Инвертировать первые
n/2узлов:head = reverseSubList(head, 1, n/2). - Инвертировать последние
n/2узлов:head = reverseSubList(head, n/2+1, n).
Если n нечётно, алгоритм будет выглядеть так:
head = reverseSubList(head, 1, n/2).head = reverseSubList(head, n/2+2, n).
Обратите внимание на вызов функции на втором шаге. Мы пропускаем две позиции, поскольку средний элемент должен остаться на месте.