между двумя границами: правая расширяет, левая чинит. Состояние не пересчитывается, а обновляется инкрементально — отсюда линейное время на задачах о подотрезках.
Ключевые тезисы
- Каждый индекс входит в один раз и выходит один раз: вложенный while не делает решение квадратичным.
- Четыре типа окна: фиксированное, переменное на максимум, переменное на минимум и со счётчиком.
- Условие применимости — монотонность: отрицательные числа при условии на сумму ломают приём.
Какую задачу решает
Подотрезков в массиве квадратично много, и наивное решение пересчитывает каждый с нуля. Между тем соседние окна отличаются на один элемент: почти вся работа делается второй раз.
превращает пересчёт в обновление. Правая граница добавляет элемент в состояние окна, левая — убирает, и каждая проходит массив ровно один раз. Отсюда там, где перебор границ давал , — и отсюда же единственное условие применимости: сдвиг границы должен влиять на условие в одну сторону.
Подробный разбор
«Найдите самый короткий подотрезок с суммой не меньше S», «самую длинную подстроку без повторов», «максимальную сумму k подряд идущих» — во всех этих задачах наивное решение перебирает пары границ. Пар , и если внутри ещё и считать сумму заново, получается .
При этом соседние окна отличаются на один-два элемента. Пересчитывать их целиком — то же самое, что каждый раз заново взвешивать корзину, в которую положили одно яблоко.
Не пересчитывать заново, а сдвигать границы и обновлять состояние инкрементально: что вошло — прибавить, что вышло — вычесть.
Правая граница идёт вперёд и пытается улучшить ответ. Левая двигается только тогда, когда перестало удовлетворять условию, и ровно настолько, чтобы оно снова стало допустимым. Возврата назад нет ни у одной из границ.
a: 2 3 1 2 4 3 1 2 цель: сумма >= 8
[2 3 1 2] сумма 8 -> длина 4
[3 1 2 4] сумма 10 -> длина 4
[2 4 3] сумма 9 -> длина 3
[4 3 1] сумма 8 -> длина 3| right | после расширения | Сумма | Сдвиг left | Лучшая длина |
|---|---|---|---|---|
| 0 | [2] | 2 | — | — |
| 1 | [2 3] | 5 | — | — |
| 2 | [2 3 1] | 6 | — | — |
| 3 | [2 3 1 2] | 8 | left → 1, сумма 6 | 4 |
| 4 | [3 1 2 4] | 10 | left → 2, сумма 7 | 4 |
| 5 | [1 2 4 3] | 10 | left → 4, сумма 7 | 3 |
| 6 | [4 3 1] | 8 | left → 5, сумма 4 | 3 |
| 7 | [3 1 2] | 6 | — | 3 |
Восемь шагов правой границы и пять сдвигов левой — тринадцать операций вместо тридцати шести пар. Ответ: три элемента, например .
left = 0
total = 0
best = inf
for right, x in enumerate(a):
total += x # вошло в окно
while total >= S: # окно допустимо -- пробуем сжать
best = min(best, right - left + 1)
total -= a[left] # вышло из окна
left += 1
return 0 if best == inf else besttotal = sum(a[:k])
best = total
for right in range(k, len(a)):
total += a[right] - a[right - k]
best = max(best, total)- Время — : каждый индекс входит в один раз и выходит один раз.
- Память — для суммы и для счётчика символов, где — размер алфавита.
- Длина окна —
right - left + 1; это выражение и есть место, где чаще всего ошибаются на единицу.
Корректность держится на монотонности. Для суммы неотрицательных чисел расширение окна вправо сумму только увеличивает, а сжатие слева — только уменьшает. Значит, для каждой правой границы существует ровно одна «пороговая» левая, и она не может оказаться левее пороговой для предыдущего шага: возвращать left назад никогда не нужно.
Отсюда же линейность, и она амортизированная, а не «по одному циклу»: вложенный while внутри for выглядит квадратично, но left за весь алгоритм сдвигается не более раз в сумме по всем итерациям.
Сломать это свойство легко: добавьте в массив отрицательные числа, и сумма перестанет быть монотонной по границе — , ставшее недопустимым, может снова стать допустимым при дальнейшем расширении. Приём в этом случае неверен, а не просто неэффективен.
| Сигнал в условии | Что он означает |
|---|---|
| «Подряд идущие», «подотрезок», «подстрока» | прямое указание на |
| «Самая длинная … , в которой выполняется условие» | переменное на максимум |
| «Самый короткий … , в котором достигается …» | переменное на минимум |
| Задана длина k | фиксированной длины |
| «Не более k различных», «не более k нулей» | со счётчиком внутри |
| Все значения неотрицательны, условие про сумму | монотонность есть — применимо |
И главный запрещающий сигнал: слово подпоследовательность вместо подотрезка. Оно разрешает пропуски, и задача уходит к динамике.
| Тип окна | Что задано | Что ищем | Задача |
|---|---|---|---|
| Фиксированное | длина k | агрегат в каждом окне | максимальная сумма k подряд, скользящее среднее |
| Переменное, на максимум | ограничение | самое длинное допустимое | подстрока без повторов |
| Переменное, на минимум | цель | самое короткое допустимое | минимальный подотрезок с суммой не меньше S |
| Со счётчиком | мультимножество символов | сколько окон подходит | все анаграммы образца в строке |
| С деком | нужен максимум внутри окна | агрегат, которого не даёт сумма | скользящий максимум — монотонный дек |
| По времени, а не по индексам | в секундах | агрегат за последние T | потоковые метрики, антифрод |
Параметр, который стоит назвать явно, — состояние окна. Для суммы это одно число, для «без повторов» — счётчик символов, для анаграмм — частот, для максимума — дек индексов. Всё остальное в шаблоне не меняется.
- Отрицательные числа при условии на сумму. Монотонность исчезает, даёт неверный ответ; нужны префиксные суммы с хеш-таблицей.
- Длина окна.
right - left + 1, а неright - left. - Забыли вычесть уходящий элемент. Состояние окна разъезжается с самим окном, и ошибка проявляется только на длинных входах.
- `if` вместо `while` при сжатии. Одного сдвига левой границы часто недостаточно, чтобы снова стало допустимым.
- Пустой ответ. Когда допустимого окна нет вовсе, ответом должен быть ноль или «нет», а не начальное значение
inf.
| Приём | Когда брать его | В чём разница |
|---|---|---|
| Префиксные суммы + хеш | есть отрицательные, нужна точная сумма k | не требуют монотонности, но стоят памяти |
| Встречные указатели | ищется пара, а не отрезок | вход отсортирован, идём с концов |
| Монотонный дек | нужен максимум внутри окна | надстройка над окном, а не замена |
| Динамика | элементы не обязаны идти подряд | состояние вместо двух границ |
| Бинарный поиск по ответу | длина окна монотонна по условию | внешний поиск плюс проверка окном |
- Почему вложенный
whileне делает решение квадратичным? - Максимальная сумма k подряд идущих — напишите обновление суммы за константу.
- Самая длинная подстрока без повторяющихся символов: что хранится в состоянии окна?
- Минимальный подотрезок с суммой не меньше S — а что изменится, если разрешить отрицательные числа?
- Все анаграммы образца в строке: как сравнивать с образцом, не пересобирая счётчик?
Четвёртый вопрос — ключевой: ответ «ничего не изменится» означает, что монотонность осталась непонятой. Проверить себя на смежных темах можно тестом по алгоритмам.
Чем заменяют
Видеолекции
Тайм-кода именно на эту тему в записях нет. Но глава «Алгоритмы и структуры данных» разобрана в курсе целиком — с той оговоркой, что место в записи придётся искать самому.
Связанные темы
Два указателя и окно
Two Pointers and Sliding Window90%
Два указателя: семейство приёмов · Алгоритмы и структуры данныхЗонтичная тема: что общего у четырёх приёмов с двумя индексами и как выбрать нужный. Сами приёмы разобраны каждый на своей странице.
Opposite-Direction Pointers90%
Встречные указатели · Алгоритмы и структуры данныхДва индекса сходятся с концов отсортированного массива. На каждом шаге отбрасывается конец, который заведомо не даст лучшего ответа, — и вместе с ним целая полоса пар.
Fast and Slow Pointers90%
Быстрый и медленный указатель · Алгоритмы и структуры данныхДва указателя идут в одну сторону с разной скоростью. Если структура зациклена, быстрый догоняет медленного — цикл находится без журнала посещённых узлов.
Same-Direction Pointers90%
Два указателя в одну сторону · Алгоритмы и структуры данныхСлияние, пересечение и запись на месте: каждому источнику свой указатель, ни один не откатывается назад. Основа сортировки слиянием и merge join в базах данных.
Prefix Sums90%
Префиксные суммы · Алгоритмы и структуры данныхОдин предварительный проход превращает сумму на любом отрезке в одно вычитание. Тот же приём вместе с хеш-таблицей находит подотрезок с заданной суммой за линейное время.
Monotonic Stack and Deque90%
Монотонный стек и дек · Алгоритмы и структуры данныхПриём поверх стека: хранить только тех, для кого ответ ещё не найден. Даёт ближайший больший элемент для всех позиций сразу и максимум в скользящем окне — за один проход.
Всё, что названо в описании, разобрано здесь же или в соседней теме — переходы под каждым определением.
Состояние окна
Сумма, счётчик символов или дек — то, что обновляется при сдвиге границы.
Разбор ниже: ФормализацияМонотонность
Расширение окна не может починить нарушенное условие. Без неё приём неверен.
Разбор ниже: Почему это работаетЧетыре типа окна
Фиксированное, на максимум, на минимум и со счётчиком внутри.
Разбор ниже: Вариации и параметрыАмортизация
Вложенный while линеен: left сдвигается не более n раз за весь проход.
Разбор ниже: Почему это работаетТема встречается в тесте по направлениям — ошибки приведут обратно на эту страницу.
Глава «Алгоритмы и структуры данных» последний раз правилась . Нашли ошибку — напишите.