Скользящее окно

Обзор

Скользящее окно - это паттерн для задач на подмассивы и подстроки. Мы поддерживаем текущий отрезок между двумя границами и меняем его постепенно, вместо того чтобы пересчитывать все заново.

Чаще всего окно задается двумя указателями: left и right. Правый указатель расширяет окно, левый - сужает его, когда условие перестает выполняться или когда мы хотим улучшить ответ.

Идея паттерна

Наивное решение для задач на подмассивы часто перебирает все начала и все концы:

for left in range(n):
  for right in range(left, n):
    пересчитать сумму или частоты

Такой подход имеет сложность O(n²) или хуже (в зависимости от того что мы делаем внутри вложенного цикла). Скользящее окно использует тот факт, что соседние окна отличаются всего одним или несколькими элементами.

Например, если мы знаем сумму окна [left..right], то при сдвиге правой границы достаточно добавить новый элемент. При сдвиге левой - вычесть старый.

Два типа окон

Есть два основных варианта.

Фиксированное окно используется, когда размер окна заранее известен. Например, нужно найти максимальную сумму подмассива длины k. Мы расширяем окно до размера k, обновляем ответ, затем одновременно убираем левый элемент и добавляем правый.

Динамическое окно используется, когда размер нужно подобрать. Например, найти самую длинную подстроку без повторов. Правый указатель расширяет окно, а левый двигается только тогда, когда нарушается условие.

Когда динамическое окно работает?

Главное свойство: при расширении окна вправо его состояние меняется предсказуемо, и после нарушения условия мы можем двигать левую границу только вперед, пока окно снова не станет допустимым.

Обычно это выглядит так:

  1. Расширяем right, пока окно может улучшить ответ или пока условие еще не нарушено.

  2. Сужаем left, когда окно стало недопустимым или когда уже можно улучшить ответ за счет меньшей длины.

  3. Левый указатель не возвращается назад, потому что если элемент уже вышел из окна, он не нужен для будущих ответов.

Такой подход работает, если добавление элемента справа монотонно меняет состояние окна: сумма растет, число уникальных символов не уменьшается, количество нарушений не падает. Тогда, сдвигая left вперед, мы рано или поздно возвращаем окно в допустимое состояние.

Примеры:

  • Самая длинная подстрока без повторов: расширяем, пока нет дубликата. Как только дубликат появился, сужаем слева, пока лишний символ не уйдет.

  • Минимальный подмассив с суммой не меньше target при положительных числах: расширяем, пока сумма мала, затем сужаем, пока сумма все еще достаточна, и ищем минимальную длину.

Если при расширении окна условие может то улучшаться, то ухудшаться непредсказуемо, например из-за отрицательных чисел, такой шаблон часто ломается.

Когда использовать

Скользящее окно часто подходит, когда:

  1. В задаче спрашивают про подмассив или подстроку.

  2. Нужно найти максимальную или минимальную длину отрезка.

  3. Нужно поддерживать сумму, количество или частоты внутри отрезка.

  4. Элементы можно добавлять и удалять с краев окна.

  5. Границы окна двигаются только вперед.

Скользящее окно не всегда работает с отрицательными числами в задачах на сумму. Если сумма может уменьшаться при расширении окна, простое правило "расширяем, пока мало, сужаем, пока много" перестает быть надежным.

Итоги

Скользящее окно превращает перебор всех отрезков в управляемое движение двух границ. Главный навык - правильно поддерживать состояние окна и понимать, когда сдвигать левую границу.

Войдите чтобы отмечать прогресс