Банк кейсов с собеседований
Двенадцать коротких задач в формате алгоритмической секции. В каждой есть вопросы к условию, код кандидата с одной ошибкой и тесты с результатами запуска.
Прочитайте условие, не заглядывая в код, и запишите свои вопросы и 4–5 тестов. Затем сравните их с таблицей и найдите тест, который поймал бы ошибку. Разбор открывайте последним. Все ответы в таблицах получены запуском кода на Python 3.12.
Среднее арифметическое
пустой входВерните среднее значение списка чисел.
- Что возвращать для пустого списка?
- Нужен float или можно округлить?
- Могут ли быть отрицательные числа?
Пустой список даёт None, иначе float.
def average(numbers):
return sum(numbers) / len(numbers)| Вызов | Ждём | Код кандидата | Итог |
|---|---|---|---|
average([]) | None | ZeroDivisionError | ошибка |
average([4]) | 4.0 | 4.0 | совпало |
average([1, 2]) | 1.5 | 1.5 | совпало |
average([-1, 1]) | 0.0 | 0.0 | совпало |
Открыть разбор
Где ошибка. Для пустого списка len равен нулю, и деление падает с ZeroDivisionError. Пустой вход нужно обработать до деления.
def average(numbers):
if not numbers:
return None
return sum(numbers) / len(numbers)Вывод для тестов. Особый вход проверяют первым тестом.
Индекс минимума
дубликатыВерните индекс наименьшего элемента непустого списка.
- Если минимум встречается несколько раз, какой индекс вернуть?
- Список может быть пустым?
- Нужен индекс или само значение?
Список непустой; при равных минимумах — индекс первого вхождения.
def index_of_min(numbers):
best = 0
for i in range(1, len(numbers)):
if numbers[i] <= numbers[best]:
best = i
return best| Вызов | Ждём | Код кандидата | Итог |
|---|---|---|---|
index_of_min([4, -1, 7]) | 1 | 1 | совпало |
index_of_min([5]) | 0 | 0 | совпало |
index_of_min([3, 1, 1]) | 1 | 2 | ошибка |
index_of_min([2, 2, 2]) | 0 | 2 | ошибка |
Открыть разбор
Где ошибка. Знак <= переносит ответ на каждое следующее равное значение, поэтому возвращается последнее вхождение. Контракт требовал первое.
def index_of_min(numbers):
best = 0
for i in range(1, len(numbers)):
if numbers[i] < numbers[best]:
best = i
return bestВывод для тестов. Дубликаты проверяют отдельным тестом, когда ответ — индекс.
Количество положительных
граничное значениеПосчитайте, сколько в списке положительных чисел.
- Ноль считается положительным?
- Что вернуть для пустого списка?
- Какие значения могут встретиться?
Положительные — строго больше нуля; пустой список даёт 0.
def count_positive(numbers):
count = 0
for value in numbers:
if value >= 0:
count += 1
return count| Вызов | Ждём | Код кандидата | Итог |
|---|---|---|---|
count_positive([2, 3]) | 2 | 2 | совпало |
count_positive([]) | 0 | 0 | совпало |
count_positive([0]) | 0 | 1 | ошибка |
count_positive([-1, 0, 1]) | 1 | 2 | ошибка |
Открыть разбор
Где ошибка. Условие >= 0 учитывает ноль. Ошибку ловит вход, где есть ноль, — например [0].
def count_positive(numbers):
count = 0
for value in numbers:
if value > 0:
count += 1
return countВывод для тестов. Значение на границе условия (здесь 0) — обязательный тест.
Удаление повторов с сохранением порядка
порядокВерните новый список без повторов, сохранив порядок первых вхождений.
- Порядок важен?
- Исходный список можно менять?
- Какие типы элементов?
Порядок первых вхождений сохраняется; вход не изменяется.
def dedupe(numbers):
return list(set(numbers))| Вызов | Ждём | Код кандидата | Итог |
|---|---|---|---|
dedupe([]) | [] | [] | совпало |
dedupe([1, 1]) | [1] | [1] | совпало |
dedupe([3, 1, 3, 2]) | [3, 1, 2] | [1, 2, 3] | ошибка |
dedupe([5, 4, 5]) | [5, 4] | [4, 5] | ошибка |
Открыть разбор
Где ошибка. Множество не хранит порядок добавления. На маленьких числах результат часто выглядит отсортированным, и порядок входа теряется.
def dedupe(numbers):
seen = set()
result = []
for value in numbers:
if value not in seen:
seen.add(value)
result.append(value)
return resultВывод для тестов. Нужен тест, в котором порядок первых вхождений не совпадает с возрастающим. Иначе версия с set может случайно пройти.
Бинарный поиск
граница циклаВ отсортированном списке найдите индекс target или верните −1.
- Список точно отсортирован по возрастанию?
- Есть ли повторы и какой индекс нужен?
- Что вернуть, если элемента нет?
Список отсортирован, повторов нет; нет элемента — −1.
def binary_search(numbers, target):
left, right = 0, len(numbers) - 1
while left < right:
middle = (left + right) // 2
if numbers[middle] == target:
return middle
if numbers[middle] < target:
left = middle + 1
else:
right = middle - 1
return -1| Вызов | Ждём | Код кандидата | Итог |
|---|---|---|---|
binary_search([1, 3, 5], 3) | 1 | 1 | совпало |
binary_search([], 1) | -1 | -1 | совпало |
binary_search([5], 5) | 0 | -1 | ошибка |
binary_search([1, 3, 5], 5) | 2 | -1 | ошибка |
binary_search([1, 3], 2) | -1 | -1 | совпало |
Открыть разбор
Где ошибка. Границы заданы включительно: [left, right]. При left == right остаётся один непроверенный элемент, а while left < right его пропускает.
def binary_search(numbers, target):
left, right = 0, len(numbers) - 1
while left <= right:
middle = (left + right) // 2
if numbers[middle] == target:
return middle
if numbers[middle] < target:
left = middle + 1
else:
right = middle - 1
return -1Вывод для тестов. Инвариант: если target есть, он внутри [left, right]. Цикл идёт, пока отрезок не пуст.
Максимальная прибыль
начальное значениеПо ценам акции за дни найдите наибольшую прибыль от одной покупки и последующей продажи.
- Можно ли не совершать сделку?
- Что вернуть, если дней меньше двух?
- Продать в день покупки можно?
Сделки может не быть — тогда 0; меньше двух дней — 0.
def max_profit(prices):
best = prices[1] - prices[0]
low = prices[0]
for price in prices[1:]:
best = max(best, price - low)
low = min(low, price)
return best| Вызов | Ждём | Код кандидата | Итог |
|---|---|---|---|
max_profit([7, 1, 5, 3, 6, 4]) | 5 | 5 | совпало |
max_profit([7, 6, 4]) | 0 | -1 | ошибка |
max_profit([5]) | 0 | IndexError | ошибка |
max_profit([]) | 0 | IndexError | ошибка |
Открыть разбор
Где ошибка. Старт prices[1] - prices[0] требует двух дней и может быть отрицательным. При падающих ценах функция вернёт убыток, хотя по контракту можно не торговать.
def max_profit(prices):
best = 0
low = None
for price in prices:
if low is not None:
best = max(best, price - low)
low = price if low is None else min(low, price)
return bestВывод для тестов. Начальное значение выводят из контракта: «сделки может не быть» значит старт 0.
Слияние отсортированных списков
завершение циклаОбъедините два отсортированных списка в один отсортированный.
- Могут ли списки быть пустыми?
- Повторы сохраняются?
- Можно ли изменять входные списки?
Пустые списки допустимы, повторы сохраняются, входы не меняются.
def merge_sorted(a, b):
i = j = 0
result = []
while i < len(a) and j < len(b):
if a[i] <= b[j]:
result.append(a[i])
i += 1
else:
result.append(b[j])
j += 1
return result| Вызов | Ждём | Код кандидата | Итог |
|---|---|---|---|
merge_sorted([1, 4], [2]) | [1, 2, 4] | [1, 2] | ошибка |
merge_sorted([], [1]) | [1] | [] | ошибка |
merge_sorted([2, 2], [2]) | [2, 2, 2] | [2, 2] | ошибка |
merge_sorted([1], [5, 6]) | [1, 5, 6] | [1] | ошибка |
Открыть разбор
Где ошибка. Цикл останавливается, когда кончился один из списков. Остаток второго не переносится в результат.
def merge_sorted(a, b):
i = j = 0
result = []
while i < len(a) and j < len(b):
if a[i] <= b[j]:
result.append(a[i])
i += 1
else:
result.append(b[j])
j += 1
result.extend(a[i:])
result.extend(b[j:])
return resultВывод для тестов. Тест с разной длиной списков и тест с пустым списком проверяют завершение цикла.
Циклический сдвиг вправо
большой параметрСдвиньте элементы списка вправо на k позиций.
- k может быть больше длины списка?
- k может быть нулём или отрицательным?
- Что делать с пустым списком?
k ≥ 0 и может превышать длину; пустой список остаётся пустым.
def rotate_right(numbers, k):
return numbers[-k:] + numbers[:-k]| Вызов | Ждём | Код кандидата | Итог |
|---|---|---|---|
rotate_right([1, 2, 3], 1) | [3, 1, 2] | [3, 1, 2] | совпало |
rotate_right([1, 2, 3], 0) | [1, 2, 3] | [1, 2, 3] | совпало |
rotate_right([1, 2, 3], 4) | [3, 1, 2] | [1, 2, 3] | ошибка |
rotate_right([], 2) | [] | [] | совпало |
Открыть разбор
Где ошибка. При k больше длины срезы берут весь список и пустой хвост, и сдвига не происходит. Сдвиг на k совпадает со сдвигом на остаток от деления k на длину списка, и это нужно учесть в коде.
def rotate_right(numbers, k):
if not numbers:
return []
k %= len(numbers)
if k == 0:
return numbers[:]
return numbers[-k:] + numbers[:-k]Вывод для тестов. Значения параметра больше размера данных — отдельный класс тестов.
Сумма цифр числа
отрицательныеВерните сумму цифр целого числа.
- Число может быть отрицательным?
- Как считать 0?
- Знак минуса учитывается?
Знак не учитывается: сумма цифр модуля; для 0 ответ 0.
def sum_digits(n):
total = 0
while n > 0:
total += n % 10
n //= 10
return total| Вызов | Ждём | Код кандидата | Итог |
|---|---|---|---|
sum_digits(123) | 6 | 6 | совпало |
sum_digits(0) | 0 | 0 | совпало |
sum_digits(7) | 7 | 7 | совпало |
sum_digits(-45) | 9 | 0 | ошибка |
Открыть разбор
Где ошибка. Для отрицательного числа условие n > 0 ложно сразу, цикл не выполняется, и ответ 0.
def sum_digits(n):
n = abs(n)
total = 0
while n > 0:
total += n % 10
n //= 10
return totalВывод для тестов. Отрицательные входы проверяют всегда, если контракт их не запрещает.
Самая длинная серия равных соседей
завершение циклаНайдите длину самой длинной серии подряд идущих одинаковых элементов.
- Что вернуть для пустого списка?
- Серия из одного элемента считается?
- Сравнивать только соседей?
Пустой список даёт 0; одиночный элемент — серия длины 1.
def longest_run(numbers):
if not numbers:
return 0
best = 0
current = 1
for i in range(1, len(numbers)):
if numbers[i] == numbers[i - 1]:
current += 1
else:
best = max(best, current)
current = 1
return best| Вызов | Ждём | Код кандидата | Итог |
|---|---|---|---|
longest_run([1, 2, 2, 3]) | 2 | 2 | совпало |
longest_run([]) | 0 | 0 | совпало |
longest_run([7]) | 1 | 0 | ошибка |
longest_run([1, 1, 2, 2, 2]) | 3 | 2 | ошибка |
Открыть разбор
Где ошибка. Лучший результат обновляется только при смене значения. Серия в самом конце списка так и не сравнивается с best.
def longest_run(numbers):
if not numbers:
return 0
best = 1
current = 1
for i in range(1, len(numbers)):
if numbers[i] == numbers[i - 1]:
current += 1
else:
current = 1
best = max(best, current)
return bestВывод для тестов. Ответ в конце входа — отдельный тест, как и ответ в начале.
Сумма чисел на отрезке
граница циклаВерните сумму целых чисел от a до b включительно.
- Обе границы включаются?
- Может ли a быть больше b?
- Числа бывают отрицательными?
a ≤ b, обе границы входят в сумму.
def sum_between(a, b):
total = 0
for x in range(a, b):
total += x
return total| Вызов | Ждём | Код кандидата | Итог |
|---|---|---|---|
sum_between(1, 3) | 6 | 3 | ошибка |
sum_between(5, 5) | 5 | 0 | ошибка |
sum_between(-2, 2) | 0 | -2 | ошибка |
sum_between(0, 4) | 10 | 6 | ошибка |
Открыть разбор
Где ошибка. range(a, b) не включает b. На отрезке из одного числа ответ сразу становится 0 вместо a.
def sum_between(a, b):
total = 0
for x in range(a, b + 1):
total += x
return totalВывод для тестов. Слово «включительно» в условии сразу проверяют тестом с a == b.
Палиндром
индексПроверьте, читается ли строка одинаково в обе стороны.
- Учитывать ли регистр и пробелы?
- Пустая строка — палиндром?
- Какие символы допустимы?
Сравниваем символы как есть; пустая строка — палиндром.
def is_palindrome(text):
for i in range(len(text) // 2):
if text[i] != text[len(text) - i]:
return False
return True| Вызов | Ждём | Код кандидата | Итог |
|---|---|---|---|
is_palindrome('') | True | True | совпало |
is_palindrome('a') | True | True | совпало |
is_palindrome('abba') | True | IndexError | ошибка |
is_palindrome('abc') | False | IndexError | ошибка |
Открыть разбор
Где ошибка. Парный индекс для i — len(text) - 1 - i. Без - 1 при i = 0 обращение идёт за конец строки, и программа падает с IndexError.
def is_palindrome(text):
for i in range(len(text) // 2):
if text[i] != text[len(text) - 1 - i]:
return False
return TrueВывод для тестов. Зеркальный индекс проверяют на строке длины 2–4 ручным прогоном.