$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогСкользящее окно: решаем задачи на подмассивы и подстроки без лишних циклов

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

$ sudo teach IT
·18 июля 2026 г.·8 мин·54
Скользящее окно: решаем задачи на подмассивы и подстроки без лишних циклов

Объясняем паттерн скользящего окна на конкретных задачах — поиск максимальной суммы подмассива, длиннейшая подстрока без повторов. Показываем, как свести O(n²) к O(n) одним приёмом.

Содержание
Что такое скользящее окно и почему оно работаетЗадача 1: максимальная сумма подмассива фиксированной длиныНаивное решение и его ценаРешение через скользящее окноТипичная ошибкаЗадача 2: длиннейшая подстрока без повторяющихся символовПостановкаПочему здесь нельзя использовать фиксированное окноНаивное решение: O(n²) или хужеРешение через скользящее окно с хеш-множествомОптимизация через хеш-таблицу: O(n) без внутреннего whileКогда скользящее окно не работаетОбобщённый шаблон переменного окнаПрактика: задача на закреплениеСложность и реальные числаВывод

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

Паттерн «скользящего окна» (sliding window) — один из фундаментальных приёмов алгоритмического мышления. Он не требует знания сложных структур данных и не предполагает математических откровений. Но он меняет способ думать о задачах на последовательностях. Разберём его честно: от идеи до конкретного кода, с типичными ошибками и нюансами.


Что такое скользящее окно и почему оно работает

Представьте, что вы читаете длинный текст и держите в голове только последние N слов. Когда вы переходите к следующему слову, вы не перечитываете всё с начала — вы просто добавляете новое слово и «забываете» самое старое. Это и есть скользящее окно.

Формально: мы поддерживаем некий «срез» массива или строки — диапазон индексов [left, right] — и двигаем его вправо, обновляя агрегированное состояние (сумму, множество символов, счётчик) инкрементально. Вместо того чтобы пересчитывать всё заново для каждой позиции, мы делаем одну операцию добавления и одну операцию удаления.

Ключевое условие применимости: задача должна обладать свойством монотонности или локальности. То есть ответ для окна [left+1, right] должен вычисляться из ответа для [left, right] за константное время. Если это условие выполнено — скользящее окно применимо.

Паттерн существует в двух вариантах:

  • Фиксированный размер окна — размер k задан заранее, окно просто движется вправо.
  • Переменный размер окна — правая граница расширяется, левая сжимается по условию. Этот вариант сложнее, но мощнее.

Задача 1: максимальная сумма подмассива фиксированной длины

Наивное решение и его цена

Классическая постановка: дан массив целых чисел, найти подмассив длины k с максимальной суммой.

code
def max_sum_naive(arr: list[int], k: int) -> int:
    n = len(arr)
    max_sum = float('-inf')
    
    for i in range(n - k + 1):
        current_sum = 0
        for j in range(i, i + k):  # вложенный цикл — O(k) на каждую позицию
            current_sum += arr[j]
        max_sum = max(max_sum, current_sum)
    
    return max_sum

Сложность: O(n × k). При k = n/2 это фактически O(n²). Для массива из 10⁶ элементов — порядка 10¹² операций. Неприемлемо.

Решение через скользящее окно

Заметим очевидное: сумма окна [i+1, i+k] отличается от суммы [i, i+k-1] ровно на два числа. Мы добавляем arr[i+k] и вычитаем arr[i]. Всё остальное остаётся неизменным.

code
def max_sum_sliding(arr: list[int], k: int) -> int:
    n = len(arr)
    if n < k:
        raise ValueError("Массив короче размера окна")
    
    # Считаем сумму первого окна
    window_sum = sum(arr[:k])
    max_sum = window_sum
    
    # Двигаем окно: добавляем правый элемент, убираем левый
    for i in range(k, n):
        window_sum += arr[i] - arr[i - k]
        max_sum = max(max_sum, window_sum)
    
    return max_sum

Сложность: O(n). Один проход по массиву, константная работа на каждом шаге.

Проверим:

code
arr = [2, 1, 5, 1, 3, 2]
k = 3
print(max_sum_sliding(arr, k))  # 9 (подмассив [2, 1, 5] → нет, [5, 1, 3] = 9)

Подмассивы: [2,1,5]=8, [1,5,1]=7, [5,1,3]=9, [1,3,2]=6. Ответ: 9. Верно.

Типичная ошибка

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


Задача 2: длиннейшая подстрока без повторяющихся символов

Это задача с переменным размером окна — и она значительно интереснее.

Постановка

Дана строка s. Найти длину наибольшей подстроки, в которой все символы уникальны.

Пример: "abcabcbb" → 3 (подстрока "abc").

Почему здесь нельзя использовать фиксированное окно

Мы не знаем заранее, какой длины будет ответ. Размер «правильного» окна меняется в зависимости от содержимого строки. Нужен другой подход.

Наивное решение: O(n²) или хуже

code
def longest_unique_naive(s: str) -> int:
    n = len(s)
    max_len = 0
    
    for i in range(n):
        seen = set()
        for j in range(i, n):
            if s[j] in seen:
                break
            seen.add(s[j])
            max_len = max(max_len, j - i + 1)
    
    return max_len

O(n²) в среднем, O(n³) в худшей реализации с проверкой подстроки через срез.

Решение через скользящее окно с хеш-множеством

Идея: расширяем правую границу right. Если s[right] уже есть в окне — сжимаем левую границу left до тех пор, пока дубликат не исчезнет.

code
def longest_unique_window(s: str) -> int:
    char_set = set()
    left = 0
    max_len = 0
    
    for right in range(len(s)):
        # Пока символ уже есть в окне — сжимаем слева
        while s[right] in char_set:
            char_set.remove(s[left])
            left += 1
        
        char_set.add(s[right])
        max_len = max(max_len, right - left + 1)
    
    return max_len

Сложность: O(n). Каждый символ добавляется в множество ровно один раз и удаляется не более одного раза. Суммарно — 2n операций.

code
print(longest_unique_window("abcabcbb"))  # 3
print(longest_unique_window("bbbbb"))     # 1
print(longest_unique_window("pwwkew"))    # 3 ("wke")
print(longest_unique_window(""))          # 0

Оптимизация через хеш-таблицу: O(n) без внутреннего while

Предыдущее решение формально O(n), но внутренний while может выполняться O(n) раз на одном шаге в худшем случае. Амортизированно всё равно O(n), но можно сделать чище — хранить не множество, а словарь с последней позицией каждого символа:

code
def longest_unique_optimized(s: str) -> int:
    last_seen = {}  # символ → последний индекс вхождения
    left = 0
    max_len = 0
    
    for right, char in enumerate(s):
        # Если символ уже видели И он внутри текущего окна
        if char in last_seen and last_seen[char] >= left:
            # Прыгаем левой границей сразу за предыдущее вхождение
            left = last_seen[char] + 1
        
        last_seen[char] = right
        max_len = max(max_len, right - left + 1)
    
    return max_len

Здесь нет внутреннего цикла вообще. Каждый символ обрабатывается ровно один раз. Проверка last_seen[char] >= left критически важна: без неё мы можем сдвинуть left назад, если символ встречался раньше текущего окна.

code
print(longest_unique_optimized("abba"))  # 2 ("ab" или "ba")
# Без проверки >= left: при 'a' на позиции 3 left сдвинулся бы на 1,
# хотя 'a' на позиции 0 уже вне окна [2, 3]

Когда скользящее окно не работает

Паттерн не универсален. Несколько случаев, где он неприменим или требует модификации:

Отрицательные числа в задачах на суммы. Задача «максимальная сумма подмассива произвольной длины» с отрицательными числами — это алгоритм Кадане, не скользящее окно. Скользящее окно не знает, когда «остановиться» при расширении, потому что добавление отрицательного элемента может как уменьшить, так и в итоге увеличить сумму.

Задачи с неаддитивными агрегатами. Если нужно поддерживать медиану или моду окна — простое добавление/удаление не даёт O(1) обновления. Здесь нужны более сложные структуры: упорядоченные множества, два heap'а.

Двумерные массивы. Скользящее окно расширяется на 2D, но требует дополнительной работы — например, предподсчёта сумм по столбцам.


Обобщённый шаблон переменного окна

Большинство задач с переменным окном укладываются в один шаблон:

code
def sliding_window_template(arr):
    left = 0
    state = ...      # текущее состояние окна (сумма, множество, счётчик)
    result = ...     # лучший найденный ответ
    
    for right in range(len(arr)):
        # 1. Расширяем окно: добавляем arr[right] в state
        state = update_add(state, arr[right])
        
        # 2. Сжимаем окно, пока условие нарушено
        while not is_valid(state):
            state = update_remove(state, arr[left])
            left += 1
        
        # 3. Обновляем результат для текущего валидного окна
        result = update_result(result, left, right, state)
    
    return result

Понимание этого шаблона позволяет быстро решать целый класс задач: минимальный подмассив с суммой ≥ target, подстрока, содержащая все символы из заданного набора (задача «Minimum Window Substring»), количество подмассивов с не более чем k различными элементами.

Именно такой подход — от абстрактного паттерна к конкретным задачам — используется в курсе «Алгоритмы и Структуры Данных на Python», где скользящее окно разбирается в контексте более широкой системы техник для работы с последовательностями.


Практика: задача на закрепление

Попробуйте самостоятельно реализовать следующее: дан массив положительных целых чисел arr и число target. Найти минимальную длину подмассива, сумма которого больше или равна target. Если такого подмассива нет — вернуть 0.

code
# Подсказка: используйте переменное окно.
# Расширяйте right, пока сумма < target.
# Как только сумма >= target — фиксируйте длину и сжимайте left.

def min_subarray_len(arr: list[int], target: int) -> int:
    left = 0
    current_sum = 0
    min_len = float('inf')
    
    for right in range(len(arr)):
        current_sum += arr[right]
        
        while current_sum >= target:
            min_len = min(min_len, right - left + 1)
            current_sum -= arr[left]
            left += 1
    
    return min_len if min_len != float('inf') else 0


print(min_subarray_len([2, 3, 1, 2, 4, 3], 7))  # 2 (подмассив [4, 3])
print(min_subarray_len([1, 1, 1, 1, 1], 11))     # 0

Обратите внимание на инверсию логики: здесь мы сжимаем окно не потому, что оно «плохое», а потому что хотим найти минимальное «хорошее».


Сложность и реальные числа

Чтобы не быть голословными — конкретные цифры. Пусть n = 10⁶:

ПодходОпераций (примерно)Время (Python, ~10⁷ оп/с)
O(n²) наивный10¹²~27 часов
O(n log n)~2 × 10⁷~2 секунды
O(n) скользящее окно~2 × 10⁶~0.2 секунды

Разница между O(n²) и O(n) на реальных данных — это разница между «программа зависла» и «программа ответила мгновенно».


Вывод

Скользящее окно — не трюк и не хак. Это способ мышления: вместо того чтобы пересчитывать всё с нуля, поддерживай инвариант и обновляй его инкрементально. Этот принцип встречается повсюду — в сетевых протоколах (TCP window), в обработке сигналов, в потоковых алгоритмах.

Освоить паттерн на уровне «применяю автоматически» можно только через практику. Начните с задач на LeetCode: #643 (Maximum Average Subarray), #3 (Longest Substring Without Repeating Characters), #76 (Minimum Window Substring) — они выстроены по нарастающей сложности и хорошо покрывают все варианты паттерна.

Если хочется разобраться с алгоритмическими техниками системно, а не по одной задаче за раз — курс «Алгоритмы и Структуры Данных на Python» даёт именно такой структурированный взгляд: от базовых паттернов до их применения в реальных задачах.

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

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Алгоритмы и Структуры Данных на Python

Курс по теме

Алгоритмы и Структуры Данных на Python

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

Открыть курс

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Простыми словами о защищенном соединении

Простыми словами о защищенном соединении

Базовый курс для обычных людей: что такое защищённое соединение, как оно устроено, что оно умеет и чего не умеет, как выбрать сервис и настроить его на своих устройствах.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Сервис, который не падает - как построить безопасную зашифрованную сеть

Сервис, который не падает - как построить безопасную зашифрованную сеть

Практический курс по безопасности сети: как собрать собственный сервис защищённого доступа - от пустого сервера до подписки с автовыбором - и, что важнее, как потом не уронить его своими руками.

Перейти к курсу

Другие статьи

FastAPI и фоновые задачи: когда лучше использовать BackgroundTasks, а когда очередь
fastapi

FastAPI и фоновые задачи: когда лучше использовать BackgroundTasks, а когда очередь

Поймём различия между синхронной обработкой, BackgroundTasks и внешними очередями. Разберём idempotency, ретраи и мониторинг фоновых процессов.

22 июля 2026 г.
980
Алгоритм «Два указателя»: решаем задачи на массивы быстро и элегантно
алгоритм

Алгоритм «Два указателя»: решаем задачи на массивы быстро и элегантно

Объясняем технику двух указателей на пяти классических задачах с разбором кода на Python. Идеальная подготовка к техническому собеседованию в IT-компанию.

17 июля 2026 г.
910
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1431
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1090
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

Помогу новичку выбрать первый язык без стресса: объясню варианты по целям (веб, боты, анализ данных), что учить в первую очередь и как не потратить месяцы впустую. В конце — краткий план старта на ближайшие недели.

2 октября 2026 г.
630

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Что такое скользящее окно и почему оно работаетЗадача 1: максимальная сумма подмассива фиксированной длиныНаивное решение и его ценаРешение через скользящее окноТипичная ошибкаЗадача 2: длиннейшая подстрока без повторяющихся символовПостановкаПочему здесь нельзя использовать фиксированное окноНаивное решение: O(n²) или хужеРешение через скользящее окно с хеш-множествомОптимизация через хеш-таблицу: O(n) без внутреннего whileКогда скользящее окно не работаетОбобщённый шаблон переменного окнаПрактика: задача на закреплениеСложность и реальные числаВывод