Перед нами техническое собеседование на позицию Senior Data Scientist в Райффайзен Банке. Вопросы охватывают Python, алгоритмы, SQL, временные ряды и бизнес-мышление. Личные данные участников мы не используем: нас интересуют сами задачи и то, как на них отвечать.
Что проверяли на собеседовании?
Интервью состояло из трёх крупных частей. Сначала кандидат реализовал скалярное произведение разреженных векторов и обсудил сложность алгоритма. Затем разобрал SQL-запрос. В финале решал банковский кейс: как оптимизировать инкассацию отделений с помощью прогноза наличных.
Такой набор заданий хорошо показывает ожидания от Senior Data Scientist. Интервьюеру важны код и теория, но ещё важнее ход мысли: какие данные нужны, сколько стоит ошибка, как проверить модель и что должно произойти после ноутбука с экспериментом.
Как посчитать скалярное произведение разреженных векторов?
Короткий ответ: хранить только пары «индекс — значение», отсортировать их по индексу и пройти по двум последовательностям двумя указателями.
Разреженный вектор содержит много нулей. Хранить каждый ноль и перемножать все позиции невыгодно. Если в первом векторе n ненулевых элементов, а во втором m, двойной цикл потребует до n × m сравнений. Два указателя уменьшают сложность до O(n + m).
def sparse_dot(
left: list[tuple[int, float]],
right: list[tuple[int, float]],
) -> float:
# Оба списка содержат пары (индекс, значение)
# и заранее отсортированы по индексу.
left_pos = 0
right_pos = 0
result = 0.0
while left_pos < len(left) and right_pos < len(right):
left_index, left_value = left[left_pos]
right_index, right_value = right[right_pos]
if left_index == right_index:
# Вклад в произведение есть только у одинаковых индексов.
result += left_value * right_value
left_pos += 1
right_pos += 1
elif left_index < right_index:
# Текущий индекс слева уже не встретится справа.
left_pos += 1
else:
# Текущий индекс справа уже не встретится слева.
right_pos += 1
return result
# Нулевые позиции не храним: 3 × 4 + 2 × 5 = 22.
print(sparse_dot([(1, 3), (7, 2)], [(1, 4), (7, 5)]))Кортеж здесь удобен как неизменяемая пара. Но сам выбор между двумя списками и списком пар редко решает задачу в одиночку. Сначала объясните структуру данных и сложность операций, затем говорите о памяти и читаемости.
Как улучшить решение при очень разных размерах векторов?
Короткий ответ: пройти по короткому вектору, а каждый его индекс искать бинарным поиском в длинном.
Если в одном векторе два ненулевых элемента, а в другом тысяча, полный проход по обоим всё ещё работает, но может быть избыточным. Для короткого списка длины n и длинного длины m бинарный поиск даст сложность около O(n log m). На собеседовании полезно сравнить оба варианта и сказать, при каком распределении данных каждый из них выгоднее.
Что нужно добавить перед выпуском кода в продакшен?
Короткий ответ: тесты, понятный контракт функции, типы, документацию, проверки входных данных и измерение скорости на реальном размере задачи.
Минимальный набор проверок включает пустые векторы, отсутствие общих индексов, отрицательные значения, один общий индекс и сильно различающиеся размеры. Отдельно нужно договориться, разрешены ли повторяющиеся индексы и гарантирована ли сортировка. Если гарантий нет, функция должна либо подготовить данные, либо явно отклонить неверный ввод.
На Senior-собеседовании стоит упомянуть профилирование. «Быстрее по асимптотике» ещё не означает «быстрее на наших данных»: сравните реализации на типичных и предельных размерах.
Как правильно рассуждать об оптимизации SQL?
Короткий ответ: сначала проверить корректность запроса, затем посмотреть EXPLAIN, объём промежуточных данных, условия соединения и подходящие индексы.
В задании фильтры применялись после соединения таблиц, и кандидат предложил сократить наборы данных до JOIN. Это разумная гипотеза, но современный оптимизатор часто сам переносит безопасные условия ближе к чтению таблиц. Поэтому сильный ответ не заканчивается переписанным запросом: нужно сравнить планы выполнения и фактическое время через EXPLAIN ANALYZE.
Также проверяем тип соединения. Для INNER JOIN перенос условия обычно не меняет смысл, а с LEFT JOIN неосторожный фильтр может изменить результат. После этого смотрим индексы по ключам соединения и селективным условиям.
Как решить бизнес-кейс с инкассацией?
Короткий ответ: прогнозировать будущий остаток наличных и принимать решение с учётом стоимости перевозки, избытка денег и риска, что клиент не сможет снять нужную сумму.
Цель нельзя свести к минимальной MAE. Банку важны деньги. Мы бы построили симулятор процесса на историческом периоде и сравнили новую стратегию с текущей: сколько рейсов потребовалось, сколько стоила перевозка, сколько денег лежало без движения и сколько раз возникал дефицит.
Признаки могут включать день недели, праздники, зарплатные дни, историю снятий и внесений, характеристики отделения и района. Для сезонности полезны лаги и агрегаты: сумма за прошлый день, среднее за неделю, максимум за несколько последних периодов.
Как учесть разную цену ошибок и выбросы?
Короткий ответ: зафиксировать стоимость недопрогноза и перепрогноза, затем выбрать асимметричную функцию потерь или считать бизнес-стоимость решения напрямую.
Недопрогноз опаснее: в отделении может не хватить наличных. Здесь уместна квантильная регрессия, которая позволяет сместить прогноз в безопасную сторону. Параметр квантиля выбирают не «на глаз», а по итоговой стоимости стратегии.
Выбросы нельзя автоматически удалять. Крупное снятие может быть ошибкой данных, а может быть редким, но важным событием. Сначала выясняем причину. Логарифмирование цели иногда помогает модели работать с длинным правым хвостом, но обратное преобразование и метрики нужно проверять на исходной денежной шкале.
Как валидировать модель и провести эксперимент?
Короткий ответ: проверять модель только на будущем относительно обучающей выборки, а внедрение сравнивать с текущей стратегией на заранее выбранных отделениях и сроке.
Обычное случайное разбиение создаёт утечку: модель видит данные из будущего. Для временного ряда обучающий период постепенно расширяется, а следующий отрезок становится проверочным.

Синим показано обучение, красным — проверка на следующем периоде. Источник: пример scikit-learn, лицензия BSD-3-Clause.
Перед экспериментом фиксируем основную метрику, допустимый риск дефицита, минимальный полезный эффект, уровень значимости и мощность теста. Затем по историческим данным оцениваем необходимое число отделений и длительность проверки. Важен не сам факт «A/B-теста», а возможность принять решение по заранее заданным правилам.
Получил ли кандидат оффер?
Нет. Кандидат претендовал на Senior-позицию и показал хорошую базу, но несколько важных мыслей появились только после подсказок: оптимальный проход по разреженным векторам, асимметричная цена ошибки, дизайн эксперимента и временная валидация.
Мы бы оценили выступление как сильное для Middle и недостаточно самостоятельное для Senior. Это не провал знаний, а точный список зон роста: проговаривать сложность сразу, связывать метрики с деньгами и доводить решение до плана внедрения.
Источники
частые вопросы
Короткие ответы
Что спрашивают на собеседовании Senior Data Scientist?
Обычно проверяют Python и алгоритмы, SQL, машинное обучение, дизайн экспериментов и умение связать техническое решение с результатом бизнеса. На Senior-уровне важно самостоятельно замечать ограничения и предлагать проверяемый план внедрения.
Как посчитать скалярное произведение разреженных векторов?
Храните только ненулевые элементы и их индексы. Если индексы отсортированы, пройдите по двум спискам двумя указателями: умножайте значения при совпадении индексов, иначе двигайте указатель с меньшим индексом.
Как проверять модель на временных данных?
Нельзя случайно перемешивать прошлое и будущее. На каждом шаге модель обучают на более раннем периоде и проверяют на следующем отрезке времени. Такой подход реализует TimeSeriesSplit.
Получил ли кандидат оффер в Райффайзен Банк?
Нет. По итоговой оценке кандидат не прошёл интервью на заявленный Senior-уровень. При этом разбор показывает хорошую базу и конкретные темы, которые можно усилить перед следующей попыткой.


