ОП.04 · Тема 05Читать → делать → проверять
Дополнительносамостоятельная практика · 12 задач

Банк кейсов с собеседований

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

Как работать с кейсом

Прочитайте условие, не заглядывая в код, и запишите свои вопросы и 4–5 тестов. Затем сравните их с таблицей и найдите тест, который поймал бы ошибку. Разбор открывайте последним. Все ответы в таблицах получены запуском кода на Python 3.12.

Тип ошибки
01

Среднее арифметическое

пустой вход

Верните среднее значение списка чисел.

Спросите до кода
  • Что возвращать для пустого списка?
  • Нужен float или можно округлить?
  • Могут ли быть отрицательные числа?
Договорились

Пустой список даёт None, иначе float.

Код кандидата
def average(numbers):
    return sum(numbers) / len(numbers)
ВызовЖдёмКод кандидатаИтог
average([])NoneZeroDivisionErrorошибка
average([4])4.04.0совпало
average([1, 2])1.51.5совпало
average([-1, 1])0.00.0совпало
Открыть разбор

Где ошибка. Для пустого списка len равен нулю, и деление падает с ZeroDivisionError. Пустой вход нужно обработать до деления.

Исправление
def average(numbers):
    if not numbers:
        return None
    return sum(numbers) / len(numbers)

Вывод для тестов. Особый вход проверяют первым тестом.

02

Индекс минимума

дубликаты

Верните индекс наименьшего элемента непустого списка.

Спросите до кода
  • Если минимум встречается несколько раз, какой индекс вернуть?
  • Список может быть пустым?
  • Нужен индекс или само значение?
Договорились

Список непустой; при равных минимумах — индекс первого вхождения.

Код кандидата
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])11совпало
index_of_min([5])00совпало
index_of_min([3, 1, 1])12ошибка
index_of_min([2, 2, 2])02ошибка
Открыть разбор

Где ошибка. Знак <= переносит ответ на каждое следующее равное значение, поэтому возвращается последнее вхождение. Контракт требовал первое.

Исправление
def index_of_min(numbers):
    best = 0
    for i in range(1, len(numbers)):
        if numbers[i] < numbers[best]:
            best = i
    return best

Вывод для тестов. Дубликаты проверяют отдельным тестом, когда ответ — индекс.

03

Количество положительных

граничное значение

Посчитайте, сколько в списке положительных чисел.

Спросите до кода
  • Ноль считается положительным?
  • Что вернуть для пустого списка?
  • Какие значения могут встретиться?
Договорились

Положительные — строго больше нуля; пустой список даёт 0.

Код кандидата
def count_positive(numbers):
    count = 0
    for value in numbers:
        if value >= 0:
            count += 1
    return count
ВызовЖдёмКод кандидатаИтог
count_positive([2, 3])22совпало
count_positive([])00совпало
count_positive([0])01ошибка
count_positive([-1, 0, 1])12ошибка
Открыть разбор

Где ошибка. Условие >= 0 учитывает ноль. Ошибку ловит вход, где есть ноль, — например [0].

Исправление
def count_positive(numbers):
    count = 0
    for value in numbers:
        if value > 0:
            count += 1
    return count

Вывод для тестов. Значение на границе условия (здесь 0) — обязательный тест.

04

Удаление повторов с сохранением порядка

порядок

Верните новый список без повторов, сохранив порядок первых вхождений.

Спросите до кода
  • Порядок важен?
  • Исходный список можно менять?
  • Какие типы элементов?
Договорились

Порядок первых вхождений сохраняется; вход не изменяется.

Код кандидата
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 может случайно пройти.

06

Максимальная прибыль

начальное значение

По ценам акции за дни найдите наибольшую прибыль от одной покупки и последующей продажи.

Спросите до кода
  • Можно ли не совершать сделку?
  • Что вернуть, если дней меньше двух?
  • Продать в день покупки можно?
Договорились

Сделки может не быть — тогда 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])55совпало
max_profit([7, 6, 4])0-1ошибка
max_profit([5])0IndexErrorошибка
max_profit([])0IndexErrorошибка
Открыть разбор

Где ошибка. Старт 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.

07

Слияние отсортированных списков

завершение цикла

Объедините два отсортированных списка в один отсортированный.

Спросите до кода
  • Могут ли списки быть пустыми?
  • Повторы сохраняются?
  • Можно ли изменять входные списки?
Договорились

Пустые списки допустимы, повторы сохраняются, входы не меняются.

Код кандидата
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

Вывод для тестов. Тест с разной длиной списков и тест с пустым списком проверяют завершение цикла.

08

Циклический сдвиг вправо

большой параметр

Сдвиньте элементы списка вправо на 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]

Вывод для тестов. Значения параметра больше размера данных — отдельный класс тестов.

09

Сумма цифр числа

отрицательные

Верните сумму цифр целого числа.

Спросите до кода
  • Число может быть отрицательным?
  • Как считать 0?
  • Знак минуса учитывается?
Договорились

Знак не учитывается: сумма цифр модуля; для 0 ответ 0.

Код кандидата
def sum_digits(n):
    total = 0
    while n > 0:
        total += n % 10
        n //= 10
    return total
ВызовЖдёмКод кандидатаИтог
sum_digits(123)66совпало
sum_digits(0)00совпало
sum_digits(7)77совпало
sum_digits(-45)90ошибка
Открыть разбор

Где ошибка. Для отрицательного числа условие n > 0 ложно сразу, цикл не выполняется, и ответ 0.

Исправление
def sum_digits(n):
    n = abs(n)
    total = 0
    while n > 0:
        total += n % 10
        n //= 10
    return total

Вывод для тестов. Отрицательные входы проверяют всегда, если контракт их не запрещает.

10

Самая длинная серия равных соседей

завершение цикла

Найдите длину самой длинной серии подряд идущих одинаковых элементов.

Спросите до кода
  • Что вернуть для пустого списка?
  • Серия из одного элемента считается?
  • Сравнивать только соседей?
Договорились

Пустой список даёт 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])22совпало
longest_run([])00совпало
longest_run([7])10ошибка
longest_run([1, 1, 2, 2, 2])32ошибка
Открыть разбор

Где ошибка. Лучший результат обновляется только при смене значения. Серия в самом конце списка так и не сравнивается с 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

Вывод для тестов. Ответ в конце входа — отдельный тест, как и ответ в начале.

11

Сумма чисел на отрезке

граница цикла

Верните сумму целых чисел от 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)63ошибка
sum_between(5, 5)50ошибка
sum_between(-2, 2)0-2ошибка
sum_between(0, 4)106ошибка
Открыть разбор

Где ошибка. 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.

12

Палиндром

индекс

Проверьте, читается ли строка одинаково в обе стороны.

Спросите до кода
  • Учитывать ли регистр и пробелы?
  • Пустая строка — палиндром?
  • Какие символы допустимы?
Договорились

Сравниваем символы как есть; пустая строка — палиндром.

Код кандидата
def is_palindrome(text):
    for i in range(len(text) // 2):
        if text[i] != text[len(text) - i]:
            return False
    return True
ВызовЖдёмКод кандидатаИтог
is_palindrome('')TrueTrueсовпало
is_palindrome('a')TrueTrueсовпало
is_palindrome('abba')TrueIndexErrorошибка
is_palindrome('abc')FalseIndexErrorошибка
Открыть разбор

Где ошибка. Парный индекс для 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 ручным прогоном.