СобеседованиеАлгоритмы· 14 минут чтения

Алгоритмические задачи на собеседовании: паттерны, 13 типовых задач с решениями и план подготовки

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

Алгоритмические задачи на собеседовании: паттерны, 13 типовых задач с решениями и план подготовки

Алгоритмические задачи на собеседовании — это короткие задачи на массивы, строки, хеш-таблицы, деревья и графы, которые нужно решить за 20–40 минут с оценкой сложности. Почти все они сводятся к 12–15 паттернам: два указателя, скользящее окно, префиксные суммы, бинарный поиск, стек, BFS и DFS, куча, динамическое программирование. Готовиться эффективнее по паттернам: 100–150 задач уровня Easy и Medium на LeetCode, решённых с разбором, плюс 5–10 пробных интервью вслух обычно хватает для секции в крупной компании.

Что такое алгоритмическая секция и кому её проходить

Алгоритмическая секция — этап технического собеседования, где кандидат решает одну-две задачи в онлайн-редакторе на выбранном языке. Её проводят многие крупные технологические компании и в России, и за рубежом, причём не только для бэкендеров: фронтендеры, мобильные разработчики, ML-инженеры и аналитики тоже нередко её проходят. Небольшие продуктовые и аутсорс-компании чаще заменяют алгоритмы практической задачей или тестовым заданием.

Что оценивает интервьюер:

  • Понимание задачи. Уточнили ли вы ограничения и граничные случаи.
  • Выбор подхода. Предложили ли наивное решение и как перешли к оптимальному.
  • Сложность. Можете ли оценить время и память в нотации O-большое.
  • Код. Работает ли он, читается ли, обрабатывает ли краевые случаи.
  • Коммуникацию. Рассуждаете ли вслух и используете ли подсказки.

O-большое (Big O) — способ описать, как растут время работы или память при увеличении входных данных. O(n) значит «пропорционально размеру входа», O(n²) — «пропорционально квадрату».

Сложность Пример Когда допустима при n = 10⁵
O(1) Доступ по индексу, поиск в хеш-таблице Всегда
O(log n) Бинарный поиск Всегда
O(n) Один проход по массиву Да
O(n log n) Сортировка Да
O(n²) Перебор всех пар Нет, уже 10¹⁰ операций
O(2ⁿ) Перебор всех подмножеств Только при n до 20–25

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

Чего ждут от Junior, Middle и Senior

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

Middle. Medium-задачи: скользящее окно, бинарный поиск, деревья, BFS/DFS, простое динамическое программирование. Ожидается самостоятельный переход от наивного решения к оптимальному и чистый код без подсказок.

Senior. Алгоритмы обычно те же Medium, иногда Hard, но вес секции ниже: больше времени уходит на проектирование систем. От сеньора ждут быстрого решения, аккуратного кода и обсуждения компромиссов: память против скорости, поведение на реальных данных, распараллеливание.

Паттерны и типовые задачи с решениями

Решения ниже написаны на Python: он компактный и хорошо читается. Логика переносится на любой язык.

Хеш-таблица: первый неповторяющийся символ

Условие. Найдите индекс первого символа строки, который встречается ровно один раз. Если такого нет, верните -1.

Разбор. Два прохода: первый считает частоты, второй ищет первый символ с частотой 1. Время O(n), память O(k), где k — размер алфавита.

from collections import Counter

def first_unique(s: str) -> int:
    counts = Counter(s)
    for i, ch in enumerate(s):
        if counts[ch] == 1:
            return i
    return -1

Хеш-таблица — первое, о чём стоит подумать, когда нужно «найти пару», «посчитать частоты» или «проверить, встречалось ли».

Два указателя: перенести нули в конец

Условие. Переставьте все нули в конец массива, сохранив порядок остальных элементов. Дополнительную память не использовать.

Разбор. Указатель write показывает, куда поставить следующий ненулевой элемент, read перебирает массив. Время O(n), память O(1).

def move_zeroes(nums: list[int]) -> list[int]:
    write = 0
    for read in range(len(nums)):
        if nums[read] != 0:
            nums[write], nums[read] = nums[read], nums[write]
            write += 1
    return nums

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

Условие. Найдите длину самой длинной подстроки без повторяющихся символов: для "abcabcbb" ответ 3.

Разбор. Окно [left, right] расширяем вправо. Если символ уже встречался внутри окна, сдвигаем left за его прошлую позицию. Проверка last_seen[ch] >= left важна: без неё на строке "abba" левая граница уедет назад.

def longest_unique_substring(s: str) -> int:
    last_seen = {}
    left = best = 0
    for right, ch in enumerate(s):
        if ch in last_seen and last_seen[ch] >= left:
            left = last_seen[ch] + 1
        last_seen[ch] = right
        best = max(best, right - left + 1)
    return best

Сигнал для скользящего окна в условии — «непрерывный подмассив» или «подстрока» и поиск максимума или минимума.

Префиксные суммы: подмассивы с суммой k

Условие. Посчитайте количество непрерывных подмассивов, сумма которых равна k. Числа могут быть отрицательными.

Разбор. Сумма подмассива от i до j равна разности префиксных сумм. Храним, сколько раз встречалась каждая префиксная сумма, и для текущей ищем prefix - k. Скользящее окно здесь не работает из-за отрицательных чисел — об этом стоит сказать вслух.

from collections import defaultdict

def subarray_sum(nums: list[int], k: int) -> int:
    count = 0
    prefix = 0
    seen = defaultdict(int)
    seen[0] = 1
    for x in nums:
        prefix += x
        count += seen[prefix - k]
        seen[prefix] += 1
    return count

Бинарный поиск: первая позиция не меньше target

Условие. В отсортированном массиве найдите индекс первого элемента, который больше или равен target.

Разбор. Ошибки в бинарном поиске — чаще всего на границах. Надёжный шаблон — полуинтервал [lo, hi): цикл до lo < hi, hi = mid без минус единицы. Время O(log n).

def lower_bound(nums: list[int], target: int) -> int:
    lo, hi = 0, len(nums)
    while lo < hi:
        mid = (lo + hi) // 2
        if nums[mid] < target:
            lo = mid + 1
        else:
            hi = mid
    return lo

Бинарный поиск применяют не только к массивам, но и к ответу: «минимальная скорость, при которой успеем за H часов» ищется перебором скорости бинарным поиском.

Монотонный стек: дни до потепления

Условие. Для каждого дня найдите, сколько дней ждать более тёплой температуры. Если потепления не будет — 0.

Разбор. Стек хранит индексы дней, для которых ответ ещё не найден, температуры в нём убывают. Новый тёплый день «закрывает» все более холодные дни на вершине стека. Каждый индекс попадает в стек и выходит из него один раз, поэтому время O(n).

def daily_temperatures(temps: list[int]) -> list[int]:
    answer = [0] * len(temps)
    stack = []
    for i, t in enumerate(temps):
        while stack and temps[stack[-1]] < t:
            j = stack.pop()
            answer[j] = i - j
        stack.append(i)
    return answer

Связный список: разворот

Условие. Разверните односвязный список.

Разбор. Идём по списку и перенаправляем ссылку next каждого узла на предыдущий. Главное — сохранить следующий узел до того, как перезаписать ссылку.

def reverse_list(head):
    prev = None
    cur = head
    while cur:
        nxt = cur.next
        cur.next = prev
        prev = cur
        cur = nxt
    return prev

Вдогонку часто просят рекурсивную версию и спрашивают, почему она тратит O(n) памяти на стек вызовов.

Деревья: проверка дерева поиска

Условие. Проверьте, является ли двоичное дерево деревом поиска (BST): все значения слева от узла меньше него, справа — больше.

Разбор. Частая ошибка — сравнивать узел только с непосредственными детьми. Правильно передавать вниз допустимый диапазон значений.

def is_valid_bst(node, low=float("-inf"), high=float("inf")) -> bool:
    if node is None:
        return True
    if not (low < node.val < high):
        return False
    return (is_valid_bst(node.left, low, node.val)
            and is_valid_bst(node.right, node.val, high))

Графы и BFS: количество островов

Условие. Дана сетка из "1" (суша) и "0" (вода). Посчитайте количество островов — групп суши, соединённых по горизонтали и вертикали.

Разбор. Перебираем клетки; встретив непосещённую сушу, увеличиваем счётчик и обходом в ширину (BFS) помечаем весь остров. Время и память O(rows × cols).

from collections import deque

def num_islands(grid: list[str]) -> int:
    if not grid:
        return 0
    rows, cols = len(grid), len(grid[0])
    seen = set()
    islands = 0
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == "1" and (r, c) not in seen:
                islands += 1
                queue = deque([(r, c)])
                seen.add((r, c))
                while queue:
                    x, y = queue.popleft()
                    for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
                        nx, ny = x + dx, y + dy
                        if (0 <= nx < rows and 0 <= ny < cols
                                and grid[nx][ny] == "1"
                                and (nx, ny) not in seen):
                            seen.add((nx, ny))
                            queue.append((nx, ny))
    return islands

BFS находит кратчайший путь в невзвешенном графе, DFS (обход в глубину) удобен для поиска компонент и циклов. Рекурсивный DFS на большой сетке может упереться в лимит глубины рекурсии — сильный кандидат упомянет это сам.

Куча: k наибольших элементов

Условие. Верните k наибольших элементов массива.

Разбор. Сортировка даёт O(n log n). Мин-куча размера k даёт O(n log k): держим в куче k лучших и выталкиваем минимальный, когда размер превышает k. При n = 10⁷ и k = 10 разница заметна.

import heapq

def top_k(nums: list[int], k: int) -> list[int]:
    heap = []
    for x in nums:
        heapq.heappush(heap, x)
        if len(heap) > k:
            heapq.heappop(heap)
    return sorted(heap, reverse=True)

Интервалы: слияние пересекающихся

Условие. Объедините пересекающиеся интервалы: [[1,3],[2,6],[8,10]] → [[1,6],[8,10]].

Разбор. Сортируем по началу, затем либо расширяем последний интервал результата, либо добавляем новый. Уточните у интервьюера, считаются ли пересекающимися [1,4] и [4,5].

def merge_intervals(intervals: list[list[int]]) -> list[list[int]]:
    intervals.sort(key=lambda x: x[0])
    merged = []
    for start, end in intervals:
        if merged and start <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], end)
        else:
            merged.append([start, end])
    return merged

Динамическое программирование: размен монет

Условие. Найдите минимальное количество монет номиналами coins, чтобы набрать сумму amount. Если нельзя — верните -1.

Разбор. Жадный алгоритм (брать самую крупную монету) ошибается: для монет 1, 3, 4 и суммы 6 он даст 4 + 1 + 1, а оптимум — 3 + 3. Динамическое программирование считает ответ для всех сумм от 0 до amount: dp[a] — минимум монет для суммы a. Время O(amount × len(coins)).

def coin_change(coins: list[int], amount: int) -> int:
    INF = amount + 1
    dp = [0] + [INF] * amount
    for a in range(1, amount + 1):
        for c in coins:
            if c <= a:
                dp[a] = min(dp[a], dp[a - c] + 1)
    return dp[amount] if dp[amount] != INF else -1

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

Перебор с возвратом: все подмножества

Условие. Верните все подмножества массива уникальных чисел.

Разбор. Backtracking (перебор с возвратом): на каждом шаге добавляем элемент, уходим в рекурсию и убираем его обратно. Подмножеств 2ⁿ, поэтому время O(n · 2ⁿ) — улучшить нельзя, ведь их все нужно вывести.

def subsets(nums: list[int]) -> list[list[int]]:
    result = []
    path = []

    def backtrack(start: int) -> None:
        result.append(path[:])
        for i in range(start, len(nums)):
            path.append(nums[i])
            backtrack(i + 1)
            path.pop()

    backtrack(0)
    return result

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

  1. Перескажите условие своими словами и уточните ограничения: размер входа, диапазон значений, возможны ли дубликаты и пустой вход.
  2. Разберите 1–2 примера вручную, включая граничный.
  3. Назовите наивное решение и его сложность. Даже перебор показывает, что вы поняли задачу.
  4. Ищите узкое место и подходящий паттерн: повторные поиски — хеш-таблица, отсортированные данные — бинарный поиск или два указателя, подмассивы — окно или префиксные суммы.
  5. Согласуйте подход с интервьюером до того, как писать код.
  6. Пишите код с понятными именами переменных, проговаривая шаги.
  7. Проверьте решение на примере, пройдя по коду построчно, и назовите итоговую сложность.

На всё это обычно есть 30–45 минут, поэтому на шаги 1–5 стоит тратить не больше 10–15 минут.

Как готовиться: план на 8 недель

Недели Темы Цель
1–2 Массивы, строки, хеш-таблицы, два указателя, сортировка 30–40 задач Easy, привычка оценивать сложность
3–4 Скользящее окно, префиксные суммы, бинарный поиск, стек 30 задач Easy и Medium
5–6 Связные списки, деревья, графы, BFS и DFS, куча 30 задач Medium
7 Динамическое программирование, backtracking 15–20 задач Medium
8 Смешанные задачи на время, пробные интервью 3–5 мок-интервью вслух

Принципы, которые экономят время:

  • Учите паттерны. Решение конкретной задачи забудется, приём останется. После каждой задачи запишите одной фразой, какой приём её решает.
  • Ограничивайте время. 30–40 минут на попытку, потом — разбор чужого решения и повтор через несколько дней.
  • Решайте вслух. Секцию проваливают не только из-за незнания, но и из-за молчания.
  • Пишите без автодополнения хотя бы часть задач: на интервью редактор может быть простым.
  • Делайте мок-интервью с друзьями или на специальных платформах: оценка со стороны быстрее показывает слабые места.

Типичные ошибки кандидатов

  • Начинают писать код в первую минуту и уходят в неверное решение.
  • Не замечают ограничений и предлагают O(n²) при n до 10⁵.
  • Ошибаются на границах: пустой массив, один элемент, переполнение, mid в бинарном поиске.
  • Молчат, и интервьюер не может помочь подсказкой.
  • Не проверяют код и сдают решение с опечаткой, которую заметили бы за минуту трассировки.
  • Заучивают решения популярных задач и теряются на небольшой модификации условия.

Live-coding, онлайн-контест и тестовое задание

Live-coding — классическая алгоритмическая секция с интервьюером: 45–60 минут, одна-две задачи, общий редактор. Оценивают и решение, и рассуждения.

Онлайн-контест (скрининг) — задачи с автоматической проверкой на платформе вроде Яндекс Контеста, HackerRank или Codility. Его проходят без интервьюера, обычно за 1–3 часа. Важны только тесты: решение должно проходить ограничения по времени и памяти.

Тестовое задание алгоритмы проверяет редко: это проект, где смотрят на архитектуру и качество кода. Если компания дала тестовое вместо алгоритмической секции, готовиться нужно иначе — к ревью кода и вопросам по проекту.

ИИ-ассистенты на алгоритмической секции

Большинство алгоритмических интервью по-прежнему проводятся без ИИ-ассистентов, и правило стоит уточнить у рекрутера заранее. Но формат меняется: Meta в 2025 году начала тестировать собеседования с разрешённым ИИ, Canva разрешила кандидатам ИИ на технических интервью, а Google во втором полугодии 2026 года пилотирует для части инженерных вакансий в США раунд с ассистентом Gemini, где вместо задачи с нуля нужно разобраться в существующем коде, найти ошибки и оптимизировать его.

Обратная тенденция тоже есть: по данным отчёта Greenhouse за 2025 год, 39% нанимающих менеджеров в США перенесли часть собеседований в офис из-за подозрений, что кандидаты на удалённых этапах пользуются ИИ.

Как распознают подсказки от нейросети, когда они запрещены: кандидат пишет оптимальное решение без промежуточных шагов, но не может объяснить инвариант цикла или оценить сложность; спотыкается, когда интервьюер меняет условие; делает долгие паузы с отведённым взглядом. Любое изменение задачи «а если числа могут быть отрицательными?» быстро показывает, понимает ли человек решение.

Если ИИ разрешён, оценивают другое: умение разбить задачу, сформулировать запрос, проверить сгенерированный код на граничных случаях и найти в нём ошибку. Этот навык всё чаще встречается в требованиях вакансий — подробнее в материалах о вайб-кодинге и ИИ-навыках программиста.

Где тренироваться

Ресурс Что там есть Кому подходит
LeetCode Тысячи задач, подборки по компаниям, еженедельные контесты Основной тренажёр для подготовки к секции
NeetCode Список задач, сгруппированных по паттернам, видеоразборы Тем, кто хочет готовиться по паттернам
CodeRun Тренажёр Яндекса с задачами для подготовки к собеседованиям Тем, кто идёт в российские компании
Codeforces Соревнования и архив задач с рейтингом Тем, кто хочет прокачать скорость и сложные задачи
Codewars Задачи-«каты» по уровням, чужие решения Новичкам для разогрева
HackerRank Задачи по темам, формат онлайн-скринингов Подготовке к автоматическим тестам компаний

Теорию удобно освежать по книге «Грокаем алгоритмы» Адитьи Бхаргавы — она объясняет базовые структуры данных на картинках. Для серьёзной подготовки к секции этого мало, но как вход в тему книга подходит.

Алгоритмическую секцию проходят разработчики, ML-инженеры и часть аналитиков. Посмотреть, какие компании сейчас нанимают, можно в каталоге вакансий в разработке, а перед откликом — обновить резюме.

Частые вопросы

Сколько задач на LeetCode нужно решить перед собеседованием?

Ориентир — 100–150 задач уровня Easy и Medium, покрывающих основные паттерны. Если вы решаете новую Medium-задачу за 30 минут без подсказок, вы готовы к большинству секций.

Какой язык выбрать для алгоритмической секции?

Тот, на котором пишете быстрее всего. Python популярен из-за краткости, Java и C++ — из-за привычных структур данных. Компании обычно разрешают любой распространённый язык.

Нужно ли учить Hard-задачи?

Для большинства компаний — нет. Hard встречаются на позициях с сильным алгоритмическим уклоном и в отдельных компаниях. Лучше уверенно решать Medium.

Что делать, если не успел решить задачу?

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

Можно ли подготовиться за неделю?

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

Пригодится ли спортивное программирование?

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