$ 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
ГлавнаяБлогPython на интервью: как решать задачи на сложность быстрее за счёт правильных инвариантов

Python на интервью: как решать задачи на сложность быстрее за счёт правильных инвариантов

$ sudo teach IT
·26 июля 2026 г.·10 мин·62
Python на интервью: как решать задачи на сложность быстрее за счёт правильных инвариантов

Сфокусируемся на навыке объяснять решение: что вы сравниваете, какие инварианты держите и почему время работы укладывается в ограничения. Дадим шаблоны рассуждений и типовые ловушки.

Содержание
Что интервьюер на самом деле проверяет в задачах на сложность1) Правильная постановка: что именно оптимизируем2) Инвариант: свойство, которое остаётся верным3) Оценка сложности как следствие инвариантаИнварианты как инструмент: как их формулировать на практикеТип 1: Инвариант “префикс/суффикс”Тип 2: Инвариант монотонности (stack/queue/two pointers)Тип 3: Инвариант “кандидатов” и доминирования (ordering/greedy)Тип 4: Инвариант “границ” (binary search / two pointers)Шаблон ответа на интервью: “что сравниваем — какие инварианты держим — почему сложность влезает”Разбор типовых задач через инварианты1) Монотонный стек: “найти ближайший элемент, который больше/меньше” за O(n)2) Двухуказатели: “найти минимальную длину/максимальную подстроку” за O(n)3) Префиксные суммы + словарь: “сколько пар/отрезков” за O(n)4) Greedy с “доминированием”: поддерживаем только недоминируемые кандидатыКак оценивать сложность в Python, чтобы не выглядело гаданиемПравило: оценка должна быть привязана к вашему алгоритмуЕщё одна важная деталь: что говорить про “while”Типовые ловушки на интервью (и как их обходить)Ловушка 1: “Я не докажу, что инвариант верный”Ловушка 2: неправильно выбранная мера сравненияЛовушка 3: off-by-one в префиксных суммахЛовушка 4: недооценка ограничений Python по памяти

На собеседованиях по Python соискателю часто дают не «алгоритм ради алгоритма», а задачу, где ключевой навык — объяснить, почему решение укладывается в ограничения. И если вы умеете писать код за 20 минут, но на сухое объяснение тратите ещё 30, интервью быстро превращается в лотерею: интервьюер слышит не уверенность, а сомнения.

Самая частая причина — отсутствие чётких инвариантов и слабая связка между ними и оценкой сложности. Вы вроде бы делаете “что-то умное”, но не можете сказать:

  • что именно вы сравниваете (и с чем);
  • какие свойства сохраняются на каждом шаге;
  • как из этого следует оценка времени/памяти.

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


Что интервьюер на самом деле проверяет в задачах на сложность

Обычно задачи выглядят так: дан массив/строка/граф, нужно найти максимум, минимальную величину, длину, число пар, проверить условие и т.п. Ограничения говорят: например, n ≤ 2e5, O(n log n) допустимо, O(n^2) нет.

Но интервьюер хочет не только «правильный ответ», а три вещи:

1) Правильная постановка: что именно оптимизируем

Умный ход начинается с фразы уровня:

«Мы хотим найти… при этом важно, что…»

Например: “найти минимальную разницу между элементами, но при этом пары должны идти в возрастающем порядке индексов”.

Если постановка размыта, инварианты расплываются следом.

2) Инвариант: свойство, которое остаётся верным

Инвариант — это формальное или полуформальное утверждение вида:

«На шаге i мы храним структуру данных, из которой следует…»

Или:

«После обработки префикса [0..i) значение X равно…»

Инвариант часто проще всего сформулировать как “что хранит структура данных” и “какое утверждение о ней всегда верно”.

3) Оценка сложности как следствие инварианта

Оценка не должна звучать как “я думаю, что будет O(n log n)”. Она должна выводиться из того, как часто выполняются операции и почему нет лишних.

Интервьюер ожидает связку:

  • какие операции вы делаете;
  • сколько их (частота);
  • почему это не выходит за пределы (и где ограничение “держит” инвариант).

Инварианты как инструмент: как их формулировать на практике

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

Тип 1: Инвариант “префикс/суффикс”

Классика динамики и накопительных схем:

  • “После обработки первых i элементов мы знаем…”
  • “Для каждого состояния мы храним минимальное/максимальное значение…”

Пример: подсчёт количества подотрезков с условием через префиксные суммы и структуру, которая “хранит историю”.

Шаблон формулировки:

«Рассматриваем префикс [0..i). Инвариант: переменная/структура S после шага i отражает все варианты для этого префикса так, что…»

Тип 2: Инвариант монотонности (stack/queue/two pointers)

Часто на сложность влияет то, что элементы “вылетают” из структуры не более одного раза.

Это стандартная причина, почему “похоже на O(n^2)”, но на самом деле O(n):

  • вы можете вытолкнуть много элементов за шаг;
  • но каждый элемент был добавлен один раз и удалён один раз.

Шаблон:

«Поддерживаем монотонный стек: значения внутри неубывают/невозрастают. Инвариант: для текущего индекса i стек содержит кандидатов, которые могли стать ответом. Каждый элемент выходит из стека не более одного раза, поэтому суммарное число операций линейно».

Тип 3: Инвариант “кандидатов” и доминирования (ordering/greedy)

В жадных задачах важно сказать, почему вы “выбрасываете” кандидатов.

Например: держим множество вариантов, где один вариант “лучше” другого (доминирует), и худшие можно удалять без потери оптимума.

Шаблон:

«Инвариант: в структуре S хранятся только недоминируемые кандидаты по признаку … Все доминируемые кандидаты не могут дать лучший ответ в будущем, поэтому их можно удалить. Это гарантирует, что размер S ограничен … и операция удаления происходит …»

Тип 4: Инвариант “границ” (binary search / two pointers)

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

«Если условие выполняется для x, то оно будет выполняться для всех y ≥ x (или y ≤ x).»

Шаблон:

«Мы ищем минимальный/максимальный x, для которого условие верно. Инвариант бинарного поиска: на каждом шаге диапазон [l, r] содержит ответ, а проверка корректно обновляет границы, потому что условие монотонно».


Шаблон ответа на интервью: “что сравниваем — какие инварианты держим — почему сложность влезает”

Вот универсальная “скелетная” структура объяснения, которую можно адаптировать к большинству задач. Её можно произнести вслух за 30–45 секунд:

  1. Постановка: “Нужно найти …, перебор всех пар/отрезков даёт O(n^2) и не проходит ограничения.”
  2. Идея: “Мы переводим задачу в форму, где можно обрабатывать элементы один раз (или логарифмически).”
  3. Сравнение: “На каждом шаге мы сравниваем текущий элемент с… и выбираем лучший кандидат по правилу …”
  4. Инвариант: “Поддерживаем инвариант: … (что именно верно после шага).”
  5. Сложность: “Операций не больше … потому что каждый элемент добавляется/удаляется/обновляет структуру … не более одного раза (или потому что есть log n факторов).”

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


Разбор типовых задач через инварианты

Ниже — несколько задачевых “паттернов”, которые на интервью встречаются снова и снова. Мы не будем «переписывать учебник», а сфокусируемся на том, как держать инварианты и объяснять сложность.

1) Монотонный стек: “найти ближайший элемент, который больше/меньше” за O(n)

Задача (типовая): для каждого индекса найти ближайший слева (или справа) элемент, который больше (или меньше) текущего.

Перебор — O(n^2). Решение — монотонный стек.

Инвариант: стек хранит кандидатов в отсортированном по значениям (монотонном) порядке и индексы всегда релевантны для будущих элементов.

Например: найдём ближайший слева элемент строго больше текущего.

code
from typing import List

def prev_greater(a: List[int]) -> List[int]:
    n = len(a)
    res = [-1] * n
    stack = []  # индексы, значения a[stack] строго убывают (чтобы найти >)

    for i, x in enumerate(a):
        # Убираем тех, кто не может быть "предыдущим большим"
        while stack and a[stack[-1]] <= x:
            stack.pop()
        res[i] = stack[-1] if stack else -1
        stack.append(i)

    return res

Как объяснить инвариант:

  • Перед обработкой i в стеке лежат индексы j < i.
  • Их значения строго убывают: a[stack[0]] > a[stack[1]] > ....
  • Поэтому верхний элемент стека — ближайший (по индексу) кандидат с нужным свойством, потому что все более “малые” кандидаты были удалены.

Почему O(n):

  • Каждый индекс i добавляется в стек один раз.
  • Каждый индекс может быть удалён один раз (в момент, когда он становится бесполезным).
  • Значит суммарное число pop по циклу — O(n), а while “не взрывает” сложность.

Именно это — инвариант “каждый элемент выталкивается не более одного раза”.

Типичная ловушка: пытаться оценивать while как O(n) на каждом шаге. Это неверно: нужно суммировать по всем шагам и опираться на “каждый элемент вынимается один раз”.


2) Двухуказатели: “найти минимальную длину/максимальную подстроку” за O(n)

Задача (типовая): минимальная длина подотрезка с суммой ≥ S или максимальная длина подстроки с ограничением по частоте.

Если все элементы положительные (или условие монотонно по правой границе), двухуказатели работают.

Инвариант окна: поддерживаем окно [l, r) так, что оно удовлетворяет или не удовлетворяет условию в предсказуемом виде, и мы двигаем границы так, чтобы не пропускать оптимум.

Пример: минимальная длина подотрезка суммы ≥ S (для положительных чисел).

code
from math import inf
from typing import List

def min_subarray_len_ge_s(a: List[int], S: int) -> int:
    n = len(a)
    l = 0
    total = 0
    ans = inf

    for r in range(n):
        total += a[r]
        # Инвариант: total — сумма текущего окна [l..r]
        # Как только окно достаточно, пытаемся сжать слева
        while total >= S:
            ans = min(ans, r - l + 1)
            total -= a[l]
            l += 1

    return ans if ans != inf else 0

Как объяснить инвариант:

  • На каждом шаге r мы расширяем окно вправо.
  • Внутренний while двигает l вправо, но только пока условие total >= S остаётся верным.
  • Значит каждое значение l “пробегает” не более одного раза, потому что l никогда не уменьшается.

Почему O(n):

  • r растёт от 0 до n-1 — O(n).
  • l также растёт от 0 до n-1 суммарно — O(n).
  • Внутренний цикл не делает больше общей работы, чем перемещения l.

Ловушка: если массив не положительный, монотонность может сломаться, и двухуказатели перестают быть корректными/быстрыми.


3) Префиксные суммы + словарь: “сколько пар/отрезков” за O(n)

Задача (типовая): число подотрезков с суммой k, или с разницей элементов и т.п.

Инвариант: на шаге i мы знаем префиксную сумму pref = sum(a[:i]) и храним частоты префиксов, чтобы быстро находить нужные предыдущие значения.

Пример: число подотрезков с суммой k.

code
from collections import defaultdict
from typing import List

def count_subarrays_sum_k(a: List[int], k: int) -> int:
    pref = 0
    res = 0
    cnt = defaultdict(int)
    cnt[0] = 1  # инвариант: пустой префикс "существует"

    for x in a:
        pref += x
        # Нужно найти j < i, чтобы pref[i] - pref[j] = k => pref[j] = pref[i] - k
        res += cnt[pref - k]
        cnt[pref] += 1

    return res

Инвариант:

  • После обработки первых i элементов словарь cnt хранит, сколько раз встречалась каждая префиксная сумма pref[j] для j < i.
  • Тогда количество подотрезков, заканчивающихся в i-1, с суммой k равно числу j, для которых pref[j] = pref[i] - k.

Почему O(n):

  • Один проход по массиву.
  • Каждая итерация делает O(1) операций словаря в среднем (хеш-таблица).
  • Итог: O(n) среднее.

Ловушка: неправильная инициализация cnt[0]=1 — частая причина “не находит подотрезки, начинающиеся с нуля”.


4) Greedy с “доминированием”: поддерживаем только недоминируемые кандидаты

Эти задачи сложнее в объяснении, потому что инвариант неочевиден. Но как только вы его формулируете, сложность становится почти автоматической.

Типовой сюжет: есть набор пар/событий, нужно выбрать максимум/минимум при ограничениях. После сортировки вы проходите слева направо, а кандидатов “можно выбрасывать”, если они хуже.

Например (упрощённо): после сортировки по x вы хотите найти максимальную длину цепочки по y с условием y не убывает. Иногда можно использовать структуру для LIS, иногда — другой greedy. Нас интересует именно объяснение инварианта “кандидатов”.

Шаблон объяснения (универсальный):

  • “Мы сортируем по первому ключу, чтобы гарантировать порядок.”
  • “Далее поддерживаем структуру S — список кандидатов, которые потенциально могут улучшить ответ.”
  • “Если новый кандидат доминирует/заменяет старый, старый можно удалить без потери оптимума, потому что для любого будущего элемента он не даст лучшего результата.”

Сложность выводится через:

  • что удалений “не слишком много” (аналогичный аргумент “каждый элемент удаляется один раз” или “размер структуры ограничен”),
  • или через log n вставки/удаления.

Ловушка: когда инвариант сформулирован как “так быстрее”, без указания критерия доминирования. Тогда оценка сложности становится “догадкой”, а не доказательством.


Как оценивать сложность в Python, чтобы не выглядело гаданием

В теории легко сказать “используем хеш-таблицу — значит O(n)”. Но в Python нужно аккуратно: dict/set работают за амортизированное O(1), сортировки — O(n log n) и т.д.

Правило: оценка должна быть привязана к вашему алгоритму

Используйте один из трёх способов объяснить сложность:

  1. Счёт операций: “цикл проходит n раз, внутри константное число действий”.
  2. Амортизация по инварианту: “каждый элемент один раз добавляется и один раз удаляется”.
  3. Структура ветвления: “есть log n уровней из-за бинарного поиска; на каждом уровне — линейная проверка” (если действительно так).

Ещё одна важная деталь: что говорить про “while”

Интервьюеры часто слышат: “внутренний while может выполниться O(n), итого O(n^2)”. Это верная мысль, но неверная оценка, если вы используете инвариант типа монотонного стека или двупойнтеров.

Правильный способ:

  • “while выглядит как вложенный цикл, но суммарное число итераций while ограничено структурой: каждый элемент покидает стек/окно один раз”.

Типовые ловушки на интервью (и как их обходить)

Ловушка 1: “Я не докажу, что инвариант верный”

Если вы не проговариваете инвариант до кода, интервьюер будет воспринимать решение как набор трюков.

Как исправить:

  • Сначала скажите инвариант, потом покажите код.
  • Обычно инвариант сформулируется как “что хранится” и “какое свойство истинно”.

Ловушка 2: неправильно выбранная мера сравнения

Например, вы ищете максимум, но сравниваете по “не тем” ключом или нарушаете стабильность условий.

Как исправить:

  • Чётко перечислите критерий: “мы сравниваем по значению a[j]”, “мы используем индексы как порядок”, “мы держим строгость </<=”.

Ловушка 3: off-by-one в префиксных суммах

Чаще всего: забыли про пустой префикс, перепутали [0..i) и [0..i].

Как исправить:

  • Всегда проговаривайте: какая префиксная сумма соответствует какому диапазону индексов.
  • Используйте единый стиль: pref для [0..i).

Ловушка 4: недооценка ограничений Python по памяти

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

Как исправить:

  • Оцени

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

Автор

$ sudo teach IT

Курс по теме

40+ задач по Python с собеседований: Яндекс, Сбер, Wildberries и Avito

Хотите пройти техническое интервью в Wildberries, Ozon, Яндекс или западный стартап? Начните с бесплатного тренажёра по Python. Это ваш реальный шанс прикоснуться к задачам с технических собеседований в BigTech.

Бесплатно
Открыть курс

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

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

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

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

Перейти к курсу
Solidity DLC - поймай, если сможешь

Solidity DLC - поймай, если сможешь

Аудит смарт-контрактов на реальных взломах. The DAO, Parity, Wormhole, Ronin, Euler и ещё 7 разборов: что пошло не так, сколько потеряли и что бы спасло. Атаки и защиты на Foundry, план входа в профессию аудитора.

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

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

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

Перейти к курсу
Solidity pro - всё или ничего

Solidity pro - всё или ничего

Как пишут контракты, которые работают в основной сети. Наследование и OpenZeppelin, паттерны продакшена, оптимизация газа, ABI, delegatecall и подписи. 7 модулей, 33 урока, а в финале собственное dApp: мини-DAO с токеном, голосованием и казной.

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

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

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость
python

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость

Поймём, где чаще всего ломается воспроизводимость (версии данных, фильтры, дрейф) и как оформить пайплайн признаков так, чтобы результаты совпадали между запусками и окружениями.

17 сентября 2026 г.
1790
Как устроен GIL в Python: что он ограничивает и как это влияет на реальный код
устроен

Как устроен GIL в Python: что он ограничивает и как это влияет на реальный код

Разберём, почему GIL мешает CPU-bound потокам, а для IO-bound иногда всё “выглядит как будто работает”. Поймём, когда выбирать потоки, процессы и asyncio, и как измерить эффект на вашей задаче.

16 сентября 2026 г.
1560
Как выучить программирование самому и бесплатно: с чего начать и в каком порядке
выучить

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

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

21 сентября 2026 г.
1960
Как начать с HTML и сделать первую страницу: пошагово и без сложных слов
html

Как начать с HTML и сделать первую страницу: пошагово и без сложных слов

Разберём, что нужно новичку, чтобы за 1–2 вечера собрать свою первую веб-страницу: структура HTML, базовые теги, ссылки и картинки, плюс простой чек-лист, как понять, что страница работает правильно.

2 ноября 2026 г.
20
Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей
cargo

Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей

Разберём, как организовать Rust workspace, управлять optional features, фиксировать версии библиотек и уменьшать “дрейф” зависимостей при командной разработке и CI.

18 сентября 2026 г.
1960
Что такое HTML и чем он отличается от CSS — объясню простыми словами и на примерах
такое

Что такое HTML и чем он отличается от CSS — объясню простыми словами и на примерах

Разберём, из чего состоит веб-страница: что делает HTML (смысл и структура) и что делает CSS (оформление). В конце соберёте мини-страницу и поймёте, что нужно учить в каком порядке.

30 октября 2026 г.
130

Комментарии

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

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

Содержание

Что интервьюер на самом деле проверяет в задачах на сложность1) Правильная постановка: что именно оптимизируем2) Инвариант: свойство, которое остаётся верным3) Оценка сложности как следствие инвариантаИнварианты как инструмент: как их формулировать на практикеТип 1: Инвариант “префикс/суффикс”Тип 2: Инвариант монотонности (stack/queue/two pointers)Тип 3: Инвариант “кандидатов” и доминирования (ordering/greedy)Тип 4: Инвариант “границ” (binary search / two pointers)Шаблон ответа на интервью: “что сравниваем — какие инварианты держим — почему сложность влезает”Разбор типовых задач через инварианты1) Монотонный стек: “найти ближайший элемент, который больше/меньше” за O(n)2) Двухуказатели: “найти минимальную длину/максимальную подстроку” за O(n)3) Префиксные суммы + словарь: “сколько пар/отрезков” за O(n)4) Greedy с “доминированием”: поддерживаем только недоминируемые кандидатыКак оценивать сложность в Python, чтобы не выглядело гаданиемПравило: оценка должна быть привязана к вашему алгоритмуЕщё одна важная деталь: что говорить про “while”Типовые ловушки на интервью (и как их обходить)Ловушка 1: “Я не докажу, что инвариант верный”Ловушка 2: неправильно выбранная мера сравненияЛовушка 3: off-by-one в префиксных суммахЛовушка 4: недооценка ограничений Python по памяти