Перейти к основному содержимому

Big O для памяти

Big O может описывать память так же, как описывает время: сколько дополнительного места нужно алгоритму по мере роста входных данных?

Мы считаем только дополнительное место — переменные и массивы, которые создаёт алгоритм, — а не сами входные данные. Это дополнительное место называют вспомогательной памятью.

Память O(1): фиксированное количество переменных, каким бы ни был вход. Память O(n): на каждый входной элемент сохраняется что-то новое, например копия или Map уже увиденных значений.


Память O(1)

Алгоритм, который хранит лишь несколько обычных переменных, использует постоянную память. В массиве может быть десять элементов или десять миллионов — самому алгоритму по-прежнему принадлежат только его счётчики.

Суммирование, поиск максимума и линейный поиск — все они имеют время O(n), но память O(1): они читают много, а запоминают почти ничего.

Сумма с одной переменной

Весь алгоритм помнит ровно одно число — sum. Его расход памяти не растёт вместе с массивом: память O(1).

Prompt
Напишите программу на TypeScript, которая суммирует массив чисел 3, 1, 4, 1, 5, 9, 2, 6, используя одну дополнительную переменную, и выводит сумму, чтобы показать алгоритм с временем O(n), но с памятью O(1).
Вывод: программа на TypeScript и захваченный вывод
Sum with one variable

Память O(n)

Как только алгоритм строит что-то с одной записью на каждый входной элемент — новый массив, Map, — его память становится O(n): удвойте вход, и память удвоится.

Само по себе это не плохо. Это просто цена, которую стоит замечать, потому что её можно обменять.

Построение нового массива

Выходной массив получает по одному элементу на каждый входной элемент. 5 на входе, 5 на выходе — память растёт вместе с входом: память O(n).

Prompt
Напишите программу на TypeScript, которая строит новый массив, где каждый элемент массива 1, 2, 3, 4, 5 удвоен, добавляя элементы в пустой массив, а затем выводит исходный массив и полученный массив.
Вывод: программа на TypeScript и захваченный вывод
Build a new array

Обмен памяти на время

Часто можно потратить память, чтобы сэкономить время. Классический пример — Two Sum: найти в массиве два числа, дающих в сумме заданное значение.

Прямой способ проверяет каждую пару — время O(n²), память O(1). Быстрый способ проходит по массиву один раз и запоминает каждое увиденное число в Map. Для каждого числа он спрашивает: «встречалось ли уже target - n?» — время O(n), память O(n).

Такой обмен обычно оправдан: память в машину добавить можно, а время — нет.

Two Sum с двумя циклами

Каждая пара проверяется, пока не найдётся совпадение — до 15 проверок для 6 элементов, и это число растёт как квадрат входа. Дополнительная память не используется.

Prompt
Напишите программу на TypeScript, которая находит два числа с суммой 9 в массиве 2, 11, 15, 1, 8, 7, проверяя каждую пару двумя вложенными циклами и подсчитывая проверки. Выведите каждое совпадение вместе с количеством проверок на этот момент, а в конце выведите общее количество проверок.
Вывод: программа на TypeScript и захваченный вывод
Two Sum with two loops

Two Sum с Map

Один проход, 6 проверок. Каждое число ищется через seen.has() и сохраняется через seen.set() — работа за постоянное время на элемент. Map — это та память O(n), которая покупает время O(n).

Prompt
Напишите программу на TypeScript, которая за один проход находит два числа с суммой 9 в массиве 2, 11, 15, 1, 8, 7: храните Map уже увиденных чисел и для каждого числа проверяйте, встречалась ли уже разность цели и этого числа. Выведите каждое совпадение, а в конце сообщите, что каждый элемент был проверен один раз.
Вывод: программа на TypeScript и захваченный вывод
Two Sum with a Map