Префиксные суммы
Идея на пальцах
Представь выписку по банковской карте, где после каждой операции указан остаток на счёте. Сколько ты потратил с 3-го по 10-е число? Не нужно складывать все чеки — достаточно взять остаток на 10-е, вычесть остаток на 2-е, и ответ готов за одно действие.
Префиксные суммы работают так же. Один раз заранее считаем «накопленный остаток» для каждой позиции массива. После этого сумма любого отрезка считается за O(1) — одним вычитанием.
Как устроен массив префиксов
Возьмём массив:
Построим prefix, где prefix[i] — сумма первых i элементов. Длина у него на 1 больше, и он начинается с нуля:
Правило построения: prefix[i+1] = prefix[i] + nums[i]. Каждое число равно предыдущему плюс очередной элемент.
💡 Ведущий ноль — главный трюк. С ним формула одинаковая для любого отрезка, и не нужен отдельный
ifдля случая «отрезок с самого начала».
Сумма отрезка одним вычитанием
Нужна сумма nums[2..4], то есть 4 + 1 + 5 = 10:
Формула: sum(l..r) = prefix[r+1] - prefix[l]. В prefix[r+1] лежит всё до r включительно. Вычитаем всё, что было до l, и остаётся ровно наш отрезок.
Шаблон кода
def build_prefix(nums):
prefix = [0] * (len(nums) + 1)
for i, x in enumerate(nums):
prefix[i + 1] = prefix[i] + x # накопленная сумма
return prefix
def range_sum(prefix, l, r):
# сумма nums[l..r] включительно
return prefix[r + 1] - prefix[l]
p = build_prefix([3, 1, 4, 1, 5, 9, 2])
print(range_sum(p, 2, 4)) # 10Сложность
| Операция | Время | Память |
|---|---|---|
Построение prefix |
O(n) | O(n) |
| Один запрос суммы | O(1) | — |
| q запросов | O(n + q) | O(n) |
Без префиксов каждый запрос стоил бы O(n), а все вместе — O(n·q). Разница огромная.
Префиксы + хеш-таблица: подсчёт подмассивов
Частый вопрос на собесе: «сколько подмассивов имеют сумму ровно k?». Перебирать все пары (l, r) слишком долго, это O(n²). Перепишем условие через префиксы:
prefix[r+1] - prefix[l] = k ⟺ prefix[l] = prefix[r+1] - k
Получается, что для каждой правой границы нужно узнать, сколько раз раньше встречалась префиксная сумма, равная текущий_префикс − k. Такой вопрос «сколько раз я уже видел это значение?» хеш-таблица решает за O(1).
Схема рассуждения:
- идём по массиву слева направо и держим текущую накопленную сумму;
- в хеш-таблице храним «префиксная сумма → сколько раз встречалась»;
- на каждом шаге сначала смотрим в таблицу, потом записываем туда текущий префикс.
Получается один проход: O(n) по времени и O(n) по памяти. Собрать это в код — твоё упражнение в практике главы. Подсказка: подумай, что положить в таблицу до первого шага, чтобы учесть подмассивы, начинающиеся с индекса 0. Вспомни наш ведущий ноль.
Когда префиксы лучше скользящего окна
Окно опирается на монотонность: расширил окно, и сумма выросла; сузил, и она уменьшилась. Это верно только для неотрицательных чисел.
Здесь при добавлении -3 сумма падает. Окно не понимает, двигать левую границу или правую, и может пропустить ответ. Префиксам знаки безразличны: формула prefix[r+1] - prefix[l] верна всегда.
| Ситуация | Что брать |
|---|---|
| Только неотрицательные числа, «самый короткий/длинный отрезок с суммой ≥ k» | Скользящее окно, O(1) памяти |
| Есть отрицательные числа | Префиксы (+ хеш-таблица) |
| Много запросов суммы на неизменном массиве | Префиксы |
| Подсчёт количества отрезков с точной суммой | Префиксы + хеш-таблица |
Как это звучит на собесе
«Посчитаю массив префиксных сумм с ведущим нулём за O(n). Тогда сумма отрезка
[l, r]равнаprefix[r+1] − prefix[l]и считается за O(1). В массиве есть отрицательные числа, поэтому окно не подходит: нет монотонности. Условие на сумму отрезка я переформулирую как поиск нужного префикса в хеш-таблице».
Интервьюеру важно услышать, что ты сам заметил проблему с отрицательными числами.
Частые ошибки
- Сдвиг на единицу. Путают
prefix[r]иprefix[r+1]. Проверь формулу на отрезке из одного элемента:prefix[i+1] − prefix[i]должно датьnums[i]. - Забыли ведущий ноль. Отрезки, начинающиеся с индекса 0, считаются неправильно.
- Переполнение. В Java, C++ и Go сумма многих
intлегко выходит за предел типа, поэтому храни префиксы вlong/long long/int64. - Окно на отрицательных числах. Код выглядит правильно и проходит простые тесты, но на хитрых падает.
- Порядок работы с хеш-таблицей. Если записать текущий префикс до проверки, можно посчитать пустой подмассив или дважды учесть один и тот же.
Теперь у тебя есть инструмент, который превращает вопрос «сумма отрезка» в одно вычитание. Дальше закрепим его на задачах.