Алгоритмы и структуры данных на собеседовании
Алгоритмическую секцию дают не только в бигтехе. Чаще всего проверяют не умение написать сортировку с нуля, а способность оценить сложность своего решения и выбрать структуру данных под задачу.
Что спрашивают
- +Сложность: Big-O по времени и памяти, амортизированная стоимость, почему средний случай хеш-таблицы не гарантия
- +Массивы и хеши: два указателя, скользящее окно, префиксные суммы, частотные словари
- +Стек, очередь, куча: где они появляются в реальных задачах и почему куча решает задачи про k-е по величине
- +Деревья и графы: обходы в глубину и ширину, кратчайшие пути, задачи на BST и trie
- +Динамическое программирование: как отличить ДП от жадности, состояние и переход, рекурсия с мемоизацией
Из чего состоит тема
Так тема разложена в тренажёре: движок ведёт прогресс по каждой подтеме отдельно и возвращает те, что просели.
- Массивы, хеши, указатели24
- Графы: обходы и пути23
- Сложность и Big-O22
- Стек, очередь, куча21
- Сортировка и бинпоиск18
- Деревья, графы, рекурсия17
- Деревья: BST, кучи, trie12
- Динамическое программирование12
- Рекурсия и backtracking9
Разборы подтем
Конспект по каждой: что это, как отвечать вслух, на чём валятся, плюс вопросы для самопроверки.
- Сортировки и бинарный поиск18 вопросов
- Сложность алгоритмов и нотация Big-O22 вопросов
- Хеш-таблицы, массивы и два указателя24 вопросов
- Стек, очередь и куча21 вопросов
- Динамическое программирование12 вопросов
- Алгоритмы на графах: обходы и кратчайшие пути23 вопросов
- Рекурсия и backtracking9 вопросов
- Деревья: BST, кучи и trie12 вопросов
- Деревья, графы и рекурсия17 вопросов
Примеры вопросов с разбором
- Проверить, есть ли в массиве из миллиона элементов дубликаты. Как уложиться в O(n)?A)Двойной вложенный цикл: сравнить каждый элемент с каждым другим и искать совпаденияB)Идти по массиву, класть виденные в set; если элемент уже там — дубликатC)Отсортировать и сравнить с соседямиD)Ничего не получится — задача требует минимум O(n²)
показать ответ и разбор
+B)Идти по массиву, класть виденные в set; если элемент уже там — дубликат// разбор: Проход по массиву с множеством виденных: на каждом шаге проверка «x in seen» амортизированно O(1), всего O(n) времени и O(n) памяти. Сортировка + сравнение соседей даёт O(n log n) — тоже приемлемо, но медленнее и меняет порядок. Вложенный цикл O(n²) на миллионе — сотни миллиардов операций, неприемлемо.
- Что описывает O-нотация (например, O(n log n))?A)Как растёт время с ростом входа, с точностью до констант и младших членовB)Точное число процессорных тактов на конкретной машине для данного входа размера nC)Объём исходного кода алгоритма в строкахD)Среднее время на случайных данных
показать ответ и разбор
+A)Как растёт время с ростом входа, с точностью до констант и младших членов// разбор: O описывает порядок роста при n→∞, отбрасывая константы и младшие члены: O(2n²+n) = O(n²). Она не про такты конкретного железа и не про строки кода. По умолчанию говорят про худший случай, если не оговорено иное.
- Counting sort сортирует за O(n + k), обходя границу n log n. Когда он уместен и в чём подвох?A)Это лучшая сортировка без недостатковB)Для строк произвольной длиныC)Когда данные уже отсортированыD)Когда ключи — целые из небольшого диапазона k: при огромном k память и время O(n+k) взрываются
показать ответ и разбор
+D)Когда ключи — целые из небольшого диапазона k: при огромном k память и время O(n+k) взрываются// разбор: Counting sort считает частоты значений в массиве размера k (диапазон ключей), поэтому не сравнивает элементы и обходит n log n. Цена: нужен целочисленный (или дискретный) ключ из ограниченного диапазона. Если k огромно (64-битные числа), массив счётчиков и время O(n+k) становятся неподъёмными. Для больших диапазонов берут radix sort.
- Куча (heap / priority queue) даёт O(1) на...A)Поиск произвольного элемента по его значению где угодно внутри структурыB)Полную сортировку всех элементов за один шагC)просмотр минимума (или максимума) на вершине; извлечение — O(log n)D)Вставку с последующим полным упорядочиванием
показать ответ и разбор
+C)просмотр минимума (или максимума) на вершине; извлечение — O(log n)// разбор: Бинарная куча держит минимум (или максимум) в корне — peek за O(1). Вставка и извлечение корня — O(log n) на просеивание. Поиск произвольного элемента не ускорен — O(n). Куча идеальна, когда нужен постоянный доступ к экстремуму: топ-k, слияние потоков, Дейкстра, планировщики.
- С чего начинают решение задачи динамическим программированием?A)Сразу пишут тройной вложенный цикл по всем индексам и перебором подбирают ответ, а формулу выводят послеB)Определяют состояние (что описывает подзадача), формулу перехода к меньшим состояниям и базовые случаиC)Сортируют вход и берут жадно наибольшие элементыD)Перебирают все варианты и выбирают лучший
показать ответ и разбор
+B)Определяют состояние (что описывает подзадача), формулу перехода к меньшим состояниям и базовые случаи// разбор: Ядро DP — правильно выбрать состояние (какие параметры однозначно задают подзадачу и её ответ), затем выписать переход (рекуррентность), связывающий состояние с меньшими, и определить базу. Если состояние и переход найдены, реализация (мемоизация или табуляция) уже механическая. Ошибки DP чаще всего не в коде, а в неудачно выбранном состоянии — избыточном или, наоборот, теряющем нужную информацию.
- Что такое топологическая сортировка и для каких графов она определена?A)Упорядочивание вершин по числу входящих рёбер; годится и для графов с циклами, порядок всё равно найдётсяB)Обход графа в ширину с сохранением расстоянийC)Сортировка рёбер по весу для построения кратчайших путейD)Линейный порядок, где каждое ребро ведёт от раннего к позднему; существует только для DAG (без циклов)
показать ответ и разбор
+D)Линейный порядок, где каждое ребро ведёт от раннего к позднему; существует только для DAG (без циклов)// разбор: Топологический порядок раскладывает вершины DAG в линию так, что все рёбра направлены слева направо — это порядок выполнения задач с зависимостями. Он существует тогда и только тогда, когда граф ориентированный и без циклов: цикл создаёт взаимную зависимость, которую линейно не разложить. Строят через DFS (по завершению) или алгоритм Кана (по нулевой входящей степени).
- Как «разделяй и властвуй» на примере сортировки слиянием (merge sort) даёт O(n log n)?A)Она попарно сравнивает каждый элемент с каждым при слиянии, отчего и набегает квадратичная стоимость n·nB)Она сортирует за линейное время, log появляется случайноC)log n уровней деления пополам, на каждом слияние обрабатывает все n элементов — итого O(n log n)D)Сложность зависит только от начального порядка данных
показать ответ и разбор
+C)log n уровней деления пополам, на каждом слияние обрабатывает все n элементов — итого O(n log n)// разбор: Merge sort делит массив надвое до одиночных элементов — это дерево рекурсии высотой log n. На каждом уровне суммарно сливаются все n элементов за линейное время. Перемножая: n работы × log n уровней = O(n log n), причём стабильно, независимо от исходного порядка (в отличие от quicksort с его худшим O(n²)). Плата — O(n) дополнительной памяти под слияние.
- Почему у обычного (несбалансированного) BST операции могут деградировать до O(n)?A)Отсортированный ввод вырождает дерево в список высотой n — операции падают до O(n)B)Из-за коллизий хешей в узлахC)У BST сложность операций — O(n)D)Из-за накладных расходов на указатели детей: когда узлов много, кэш процессора промахивается и всё замедляется
показать ответ и разбор
+A)Отсортированный ввод вырождает дерево в список высотой n — операции падают до O(n)// разбор: Сложность операций BST пропорциональна высоте дерева. При случайных вставках высота ~log n, но если ключи приходят уже отсортированными, каждый следующий больше (или меньше) предыдущего и цепляется к одной ветке — дерево превращается в линейный список высотой n. Тогда поиск/вставка/удаление становятся O(n). Отсюда самобалансирующиеся деревья (AVL, красно-чёрные), удерживающие высоту логарифмической.
- Что обязательно должно быть у корректной рекурсии?A)Обязательно хотя бы два входных аргумента, иначе рекурсия не сможет корректно ветвитьсяB)Глобальная переменная-счётчик вызововC)Базовый случай (условие остановки) и движение к нему на каждом шагеD)Возврат значения через print
показать ответ и разбор
+C)Базовый случай (условие остановки) и движение к нему на каждом шаге// разбор: Рекурсия без базового случая (или без гарантированного приближения к нему) не завершается и упирается в переполнение стека вызовов (RecursionError). Два обязательных ингредиента: (1) база — когда перестаём вызывать себя; (2) рекурсивный шаг, уменьшающий задачу к базе. Число аргументов и способ вывода к корректности отношения не имеют.
это 9 из 158
Ещё 149 вопросов по теме — в тренажёре, с движком повторения
Прочитать разбор и ответить самому — разные навыки. В Сеньорчике вопросы приходят сессиями, а подтему, на которой ты споткнулся, движок принесёт снова: завтра, через три дня, через неделю. Бесплатно, с дневным лимитом вопросов.
Частые вопросы
Нужно ли решать литкод, чтобы пройти алгоритмическую секцию?
Практика задач помогает, но одного перебора задач мало. На собесе просят вслух объяснить сложность, обосновать выбор структуры и разобрать краевые случаи, а это отдельный навык от написания кода.
Какие темы встречаются чаще всего?
Хеш-таблицы и два указателя, обходы дерева и графа, сортировки и бинарный поиск, стек и куча. Динамическое программирование дают реже, но именно на нём чаще всего останавливаются.
Спрашивают ли алгоритмы у аналитиков и дата-инженеров?
Спрашивают, но в более лёгком виде: сложность операций, выбор структуры под задачу, базовые обходы. Тяжёлое ДП и графы обычно остаются в секции для разработчиков.