Алгоритмы
Сложность, поиск, сортировки, обход графов и динамическое программирование.
21 вопросов
JuniorТеорияОчень частоЧто описывает нотация Big-O?
Что описывает нотация Big-O?
Big-O — асимптотическая верхняя граница того, как растёт время или память алгоритма при росте входа n, без учёта констант и младших слагаемых. Она про масштабируемость, а не абсолютное время в секундах на входе.
Типичные ошибки
- ✗Читать Big-O как точное время в секундах, а не как границу скорости роста
- ✗Забывать, что константы и младшие слагаемые отбрасываются, поэтому меньший O может проигрывать на малых входах
Уточняющие вопросы
- →В чём разница между Big-O, Big-Theta и Big-Omega?
- →Почему алгоритм
O(n^2)может обгонятьO(n log n)на малых входах?
JuniorТеорияОчень частоКак работает бинарный поиск и что ему нужно?
Как работает бинарный поиск и что ему нужно?
Бинарный поиск делит ОТСОРТИРОВАННЫЙ массив пополам: сравнивает средний элемент с целью, отбрасывает половину, что не может её содержать, повторяет — O(log n). Данные нужно сначала отсортировать, иначе цель попадёт в отброшенную часть.
Типичные ошибки
- ✗Применять бинарный поиск к неотсортированным данным и получать неверный результат
- ✗Ошибки на единицу в индексе
midили в границах цикла
Уточняющие вопросы
- →Как найти крайнюю левую точку вставки для дублирующегося значения?
- →Что предоставляет модуль
bisectв Python для отсортированных последовательностей?
JuniorТеорияОчень частоЧто такое линейный поиск и какова его сложность?
Что такое линейный поиск и какова его сложность?
Линейный поиск перебирает элементы по одному, пока не найдёт цель или не исчерпает коллекцию — O(n). Он не требует упорядочивания и работает с любой последовательностью, но медленный на больших входах, ведь касается каждого элемента.
Типичные ошибки
- ✗Путать сложность линейного поиска
O(n)со сложностью бинарного поискаO(log n) - ✗Считать, что линейному поиску нужен отсортированный вход — он работает в любом порядке
Уточняющие вопросы
- →Когда линейный поиск предпочтительнее бинарного, несмотря на медленность?
- →Сколько в среднем сравнений делает успешный линейный поиск?
MiddleКодОчень частоTwo Sum: индексы двух чисел с суммой, равной цели
Two Sum: индексы двух чисел с суммой, равной цели
Используйте словарь «значение → индекс»: для каждого n, если target - n уже встречалось, верните оба индекса; иначе сохраните n -> i. Один проход, O(n) время и O(n) память — против перебора двойным циклом за O(n²). enumerate удобно даёт индекс.
Типичные ошибки
- ✗Возвращать значения, а не их индексы
- ✗Использовать один и тот же элемент дважды для пары
- ✗Соглашаться на двойной цикл за O(n²), когда ожидается O(n)
Уточняющие вопросы
- →Как изменить решение, если список уже отсортирован?
- →Что меняется, если верных пар может быть несколько или ни одной?
JuniorКодЧастоВернуть медиану трёх целых без сортировки
Вернуть медиану трёх целых без сортировки
Медиана — значение, не равное одновременно максимуму и минимуму. Чистый приём: a + b + c - max(a, b, c) - min(a, b, c). На сравнениях медиана — та, что лежит между двумя другими, например if (a <= b <= c) or (c <= b <= a): return b, и так далее. Равные значения обрабатываются, ведь <= сохраняет совпадения корректными.
Типичные ошибки
- ✗Безусловно возвращать
b, игнорируя, что медианой может быть любой аргумент - ✗Забывать случаи равенства вроде
a == b == cили двух равных входов - ✗Путать медиану (среднее по величине) со средним арифметическим
Уточняющие вопросы
- →Почему
sum - max - minверно даёт медиану ровно трёх значений? - →Какие тестовые входы поймают решение, неверно обрабатывающее равные значения?
JuniorТеорияЧастоКак работает быстрая сортировка?
Как работает быстрая сортировка?
Выбираем опорный элемент, разбиваем массив на меньшие и большие него, затем рекурсивно сортируем каждую часть; база рекурсии — 0 или 1 элемент. Средний и лучший случай — O(n log n), и работает на месте.
Типичные ошибки
- ✗Путать шаг разбиения быстрой сортировки со слиянием в сортировке слиянием
- ✗Забывать базу рекурсии в 0 или 1 элемент, что ведёт к бесконечной рекурсии
Уточняющие вопросы
- →Как выбор опорного элемента влияет на производительность быстрой сортировки?
- →Почему быстрая сортировка часто быстрее на практике, чем слиянием, при равном Big-O?
MiddleТеорияЧастоДля чего используется поиск в ширину?
Для чего используется поиск в ширину?
Поиск в ширину обходит граф уровень за уровнем через очередь, посещая всех соседей прежде, чем идти глубже — O(V + E). Он сообщает, есть ли путь, и находит КРАТЧАЙШИЙ путь в НЕВЗВЕШЕННОМ графе.
Типичные ошибки
- ✗Использовать поиск в ширину для кратчайших путей во взвешенных графах, где нужен
Dijkstra - ✗Заменять очередь на стек, что превращает поиск в ширину в поиск в глубину
Уточняющие вопросы
- →Как восстановить сам кратчайший путь после завершения поиска в ширину?
- →Зачем поиску в ширину нужно множество посещённых, чтобы избежать зацикливания на циклах?
MiddleТеорияЧастоЧто такое динамическое программирование?
Что такое динамическое программирование?
Динамическое программирование оптимизирует задачи с оптимальной подструктурой и перекрывающимися подзадачами: решаем каждую подзадачу раз и храним результат в таблице или мемо для повторного использования, избегая пересчёта. Классика — рюкзак и LCS.
Типичные ошибки
- ✗Называть мемоизированную рекурсию обычной и упускать шаг кэширования
- ✗Применять динамическое программирование к задачам без перекрывающихся подзадач, где оно бесполезно
Уточняющие вопросы
- →В чём разница между нисходящей мемоизацией и восходящей табуляцией?
- →Как распознать оптимальную подструктуру в новой задаче?
MiddleКодЧастоВычислите Фибоначчи итеративно и как генератор
Вычислите Фибоначчи итеративно и как генератор
Итерируйте обменом кортежа: a, b = 0, 1; for _ in range(n): a, b = b, a + b; return a — O(n) время, O(1) память, без экспоненциальной рекурсии. Генератор выдаёт лениво: while True: yield a; a, b = b, a + b. Целые в Python произвольной точности, поэтому переполнения нет.
Типичные ошибки
- ✗Наивная двойная рекурсия (экспоненциальное время)
- ✗Утверждать, что полный список — это O(1) память
- ✗Доверять, что формула Бине на float останется точной для больших n
Уточняющие вопросы
- →Почему обмен кортежа
a, b = b, a + bработает за один шаг? - →Как генераторная версия позволяет вызывающему взять лишь первые k значений?
MiddleТеорияЧастоЧто такое жадный алгоритм?
Что такое жадный алгоритм?
Жадный алгоритм строит решение, всегда беря локально оптимальный выбор на каждом шаге, в надежде прийти к глобальному оптимуму. Он прост и быстр, поэтому хорош как приближение, когда точное решение медленно, но не всегда оптимален.
Типичные ошибки
- ✗Считать, что жадный выбор всегда даёт глобальный оптимум
- ✗Путать жадный алгоритм с исчерпывающим полным перебором всех комбинаций
Уточняющие вопросы
- →Какими свойствами должна обладать задача, чтобы жадный алгоритм был доказуемо оптимален?
- →Приведите пример, где жадный алгоритм проваливается, а динамическое программирование — нет.
MiddleКодЧастоПроверка палиндрома без учёта пунктуации за O(n)
Проверка палиндрома без учёта пунктуации за O(n)
Используйте два указателя: left в начале и right в конце. Двигайте каждый мимо не-букв, затем сравните s[left].lower() с s[right].lower(); при несовпадении верните False, иначе сдвиньте оба внутрь. Остановитесь, когда они пересекутся. Это O(n) время и O(1) память — без новой отфильтрованной строки. Инициализация right как len(s)-1 (а не -1) спасает от ошибки на единицу.
Типичные ошибки
- ✗Выделять новую отфильтрованную строку, получая O(n) память вместо O(1)
- ✗Забывать привести символы к нижнему регистру перед сравнением
- ✗Ошибка на единицу от инициализации правого указателя как
-1вместоlen(s)-1
Уточняющие вопросы
- →Как два указателя пропускают пунктуацию без построения новой строки?
- →Какие крайние случаи (пустая строка, только пунктуация) должен обработать цикл?
MiddleКодЧастоНайти максимум циклически сдвинутого отсортированного массива за O(log n)
Найти максимум циклически сдвинутого отсортированного массива за O(log n)
Бинарный поиск точки сдвига. Сравните nums[mid] с nums[high]: если nums[mid] > nums[high], пик в правой половине (low = mid + 1), иначе он в mid или левее (high = mid). Максимум — элемент прямо перед точкой сдвига: nums[low - 1], когда low встанет на минимум. O(log n); отсортированный массив без сдвига вернёт свой последний элемент.
Типичные ошибки
- ✗Скатываться к линейному проходу O(n) вместо бинарного поиска
- ✗Считать, что максимум всегда в индексе
0или последнем - ✗Неверно обрабатывать сдвиг
0, когда массив уже отсортирован
Уточняющие вопросы
- →Почему сравнения
nums[mid]сnums[high]достаточно для выбора половины? - →Как меняется подход, если допускаются повторяющиеся значения?
JuniorКодИногдаОтфильтровать виденные id, сохранив исходный порядок
Отфильтровать виденные id, сохранив исходный порядок
Преобразуйте seen_ids в set один раз, затем пройдите recom_ids, оставляя элементы, чей id not in этого множества. Множество даёт O(1) проверку принадлежности, поэтому весь проход O(n); проверка in по списку сделала бы каждый тест O(m), а итог O(n*m). Проход по recom_ids напрямую сохраняет порядок.
Типичные ошибки
- ✗Оставлять
seen_idsсписком, так что каждая проверкаinO(m), а итог O(n*m) - ✗Использовать разность множеств, теряя нужный порядок
recom_ids - ✗Менять
recom_idsна месте во время итерации вместо построения нового списка
Уточняющие вопросы
- →Почему преобразование
seen_idsв множество меняет общую сложность? - →Как сохранить порядок, если всё же использовать разность множеств?
JuniorКодИногдаМодуль разности двух диагоналей квадратной матрицы
Модуль разности двух диагоналей квадратной матрицы
Пройдите i от 0 до n-1, суммируя m[i][i] для главной диагонали и m[i][n-1-i] для побочной, затем верните abs(main - anti). Ключевой индекс побочной — n-1-i; ошибка на единицу здесь (использование n-i или счёт снизу вверх) — классический баг. Один проход — O(n).
Типичные ошибки
- ✗Использовать
n-iвместоn-1-iдля столбца побочной диагонали - ✗Забывать брать модуль разности
- ✗Не приводить разобранные строковые ячейки к
intперед суммированием
Уточняющие вопросы
- →Почему индекс столбца побочной диагонали
n-1-i, а неn-i? - →Как прочитать эту матрицу из многострочной строки перед суммированием?
MiddleКодИногдаУдалить дубликаты из списка, сохранив порядок
Удалить дубликаты из списка, сохранив порядок
Самый чистый способ с сохранением порядка — list(dict.fromkeys(items)): ключи словаря уникальны и с 3.7 хранят порядок вставки. Эквивалентно: пройти один раз и добавлять элементы, которых ещё нет в множестве seen, что даёт O(n). Простой set(items) убирает дубликаты, но теряет порядок, поэтому не удовлетворяет требованию.
Типичные ошибки
- ✗Использовать
set(items), считая, что порядок сохранится - ✗Просматривать результирующий список на каждый элемент, делая O(n^2)
- ✗Сортировать сначала и утверждать, что исходный порядок сохранён
Уточняющие вопросы
- →Почему
dict.fromkeysсохраняет порядок, аsetнет? - →Как убрать дубликаты из списка нехешируемых элементов, например словарей?
MiddleТеорияИногдаЧто вычисляет алгоритм Dijkstra и каково ограничение?
Что вычисляет алгоритм Dijkstra и каково ограничение?
Dijkstra находит путь наименьшего суммарного веса от источника во ВЗВЕШЕННОМ графе — ориентированном или нет, с циклами или без — пока веса рёбер НЕОТРИЦАТЕЛЬНЫ. Для отрицательных весов используйте Bellman-Ford.
Типичные ошибки
- ✗Запускать
Dijkstraна графе с отрицательными весами вместоBellman-Ford - ✗Считать, что
Dijkstraтребует DAG, тогда как циклы вполне допустимы
Уточняющие вопросы
- →Почему отрицательный вес ребра ломает жадную корректность
Dijkstra? - →Как очередь с приоритетом улучшает временную сложность
Dijkstra?
MiddleТеорияИногдаКак работает алгоритм k ближайших соседей?
Как работает алгоритм k ближайших соседей?
Для классификации или регрессии kNN находит k обучающих точек, ближайших к запросу по метрике вроде евклидовой, и предсказывает по ним — большинством класса или усреднением. Алгоритм ленивый, без фазы обучения.
Типичные ошибки
- ✗Думать, что
k— это число признаков, а не количество соседей - ✗Забывать масштабировать признаки, позволяя одному измерению с большим диапазоном доминировать в расстояниях
Уточняющие вопросы
- →Как выбор
kбалансирует смещение и разброс? - →Почему kNN становится медленным и ненадёжным в пространствах высокой размерности?
SeniorТеорияИногдаКогда быстрая сортировка становится O(n^2) и как этого избежать?
Когда быстрая сортировка становится O(n^2) и как этого избежать?
Худший случай O(n^2) возникает при плохих опорных элементах — например, всегда брать первый или последний на отсортированном входе, что даёт максимально несбалансированные части. Смягчают это случайным опорным или медианой из трёх.
Типичные ошибки
- ✗Считать быструю сортировку
O(n log n)во всех случаях, игнорируя худшийO(n^2) - ✗Брать фиксированный первый или последний опорный на отсортированных данных и попадать в вырожденный случай
Уточняющие вопросы
- →Как introsort переключается на пирамидальную сортировку, гарантируя
O(n log n)в худшем случае? - →Почему медиана из трёх снижает шанс несбалансированных разбиений?
MiddleКодРедкоСуммарная неудовлетворённость покупателей по ближайшим товарам
Суммарная неудовлетворённость покупателей по ближайшим товарам
Отсортируйте goods один раз, затем для каждой потребности найдите точку вставки через bisect_left и сравните соседа снизу и сверху, взяв меньшее расстояние abs. Сумма даёт ответ за O((n+m) log n). Перебор — проход по всем товарам на покупателя — это O(n*m). Кандидаты вокруг индекса вставки — единственные два, что могут быть ближайшими.
Типичные ошибки
- ✗Соглашаться на проход O(n*m) по покупателю вместо бинарного поиска
- ✗Проверять лишь одного соседа точки вставки, упуская более близкую сторону
- ✗Спаривать отсортированные списки по индексам, игнорируя неограниченный запас
Уточняющие вопросы
- →Почему нужно проверять обоих соседей индекса
bisect_left? - →Как слияние двумя указателями по двум отсортированным спискам даёт ту же оценку?
MiddleКодРедкоОтсортировать огромный файл байтов, не помещающийся в память
Отсортировать огромный файл байтов, не помещающийся в память
Раз диапазон значений ограничен (256 значений байта), используйте сортировку подсчётом: читайте файл порциями, считайте частоту каждого байта в массиве из 256 ячеек, затем запишите каждое значение повторённым по его счётчику. Это O(n) время и O(1) доп. память (фиксированная таблица на 256 записей). Общий неограниченный случай потребовал бы внешней сортировки слиянием: разбить на отсортированные блоки на диске, затем слить.
Открыть задачу →Типичные ошибки
- ✗Пытаться загрузить весь файл в память вопреки ограничению размера
- ✗Упускать, что 256 ограниченных значений дают сортировку подсчётом за O(n)
- ✗Считать, что порядок вставки в dict совпадает с сортировкой по ключу
Уточняющие вопросы
- →Почему ограниченный диапазон байта превращает задачу в O(n)?
- →Как сортировать файл, если значения — неограниченные 64-битные целые?
SeniorТеорияРедкоКак распознать NP-полную задачу?
Как распознать NP-полную задачу?
Признаки: задача резко замедляется при росте входа, не имеет точного решения за полиномиальное время, словно требует перебора всех комбинаций и сводится к известной NP-полной задаче вроде покрытия множества. Для них берут приближение.
Типичные ошибки
- ✗Отождествлять NP-полноту с неразрешимостью или буквальной невозможностью решения
- ✗Считать, что есть точный полиномиальный алгоритм, когда практичны лишь приближения
Уточняющие вопросы
- →Что доказывает полиномиальное сведение об отношении двух задач?
- →Почему
P = NPостаётся одним из главных открытых вопросов информатики?