RL для моделирования молекул
Как сформулировать RL-задачу для оптимизации молекул и почему direct optimization может быть недостаточной?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
Состояние - молекула/граф, действие - допустимая модификация, reward связан с целевым свойством и штрафами за невалидность. Direct optimization часто игнорирует constraints и trajectory.
Подробный разбор
RL-постановка задает состояние как текущую молекулу или ее графовое представление, действия как допустимые изменения структуры, а reward как комбинацию целевого свойства, валидности, synthesizability, токсичности и diversity. Среда проверяет, что шаг приводит к допустимой молекуле.
Прямая оптимизация одного score может находить артефакты: невалидные структуры, нереалистичные модификации или молекулы вне допустимого химического пространства. RL полезен, когда важны последовательные изменения, constraints на действия и trade-off между несколькими критериями.
Почему RL в трейдинге опасен
Какие риски возникают при применении RL к trading/market-making задачам?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
Главный риск - неверная среда. Агент оптимизирует симулятор, а не рынок, переобучается на микроструктурные артефакты и не учитывает impact, latency и regime shifts.
Подробный разбор
RL в трейдинге требует среды, которая реалистично моделирует order book, latency, fills, transaction costs, market impact, queue position и поведение других участников. Если симулятор неточен, агент учится эксплуатировать его слабости, а не зарабатывать на реальном рынке.
Дополнительные риски: non-stationarity, редкие стресс-сценарии, leakage через будущие данные, нестабильный reward и сложность offline evaluation. Поэтому RL-идею обычно сравнивают с supervised/bandit/baseline стратегиями, добавляют risk limits и тестируют на walk-forward slices с консервативным rollout.
Обход бинарного дерева зигзагом
Дано бинарное дерево. Нужно вернуть значения узлов по уровням, но направление обхода чередуется:
- первый уровень слева направо;
- второй уровень справа налево;
- третий снова слева направо.
Signature
zigzag_level_order(root: TreeNode | None) -> list[list[int]]
В проверках дерево строится из level-order tokens и передается в вашу функцию как TreeNode.
Решение прямо на странице
Напишите код, запустите проверки и только потом открывайте разбор.
Нажмите «Запустить проверки» или Ctrl+Enter.
Показать разбор
Подсказки
- Идите по уровням
Используйте BFS и в начале каждого уровня сохраняйте текущий размер очереди.
- Меняйте направление по уровню
Соберите level, потом при необходимости разверните его.
Идея решения
Строим дерево из массива level-order, затем запускаем BFS по уровням.
На каждом уровне собираем значения слева направо. Если текущий уровень должен идти справа налево, разворачиваем список значений перед добавлением в ответ.
Ключевой момент: направление меняется после каждого уровня, а не после каждого узла.
Эталонный код
def zigzag_level_order(tokens: list) -> list[list[int]]:
from collections import deque
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def build_tree(values):
if not values or values[0] is None:
return None
root = TreeNode(values[0])
queue = deque([root])
i = 1
while queue and i < len(values):
node = queue.popleft()
if i < len(values) and values[i] is not None:
node.left = TreeNode(values[i])
queue.append(node.left)
i += 1
if i < len(values) and values[i] is not None:
node.right = TreeNode(values[i])
queue.append(node.right)
i += 1
return root
root = build_tree(tokens)
if not root:
return []
result = []
queue = deque([root])
left_to_right = True
while queue:
level_size = len(queue)
level = []
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
if not left_to_right:
level.reverse()
result.append(level)
left_to_right = not left_to_right
return resultLRU Cache с операциями get/put
Реализуйте LRU cache с фиксированной вместимостью.
Кеш поддерживает:
get(key): вернуть значение или-1, если ключа нет;put(key, value): положить или обновить значение.
После get и put ключ считается recently used. Если capacity превышена, удаляется least recently used ключ.
Signature
class LRUCache:
def __init__(self, capacity: int): ...
def get(self, key: int) -> int: ...
def put(self, key: int, value: int) -> None: ...
В проверках список операций проигрывается против вашего класса.
Решение прямо на странице
Напишите код, запустите проверки и только потом открывайте разбор.
Нажмите «Запустить проверки» или Ctrl+Enter.
Показать разбор
Подсказки
- Почему одного dict мало
dict быстро ищет ключ, но не говорит, какой ключ least recently used.
- Обновление тоже usage
Если ключ обновили через put, он становится недавно использованным.
Идея решения
Нужны две структуры данных:
- словарь для доступа к ключу за O(1);
- порядок использования, чтобы быстро удалить самый старый ключ.
В Python это удобно выразить через OrderedDict: при get и обновлении ключ переносится в конец, а при переполнении удаляется первый элемент.
На реальном собеседовании могут попросить реализовать то же самое вручную через dict[key] -> node и двусвязный список.
Эталонный код
def lru_cache_interview(capacity: int, operations: list[list]) -> list[int]:
from collections import OrderedDict
cache = OrderedDict()
result = []
for op in operations:
if op[0] == 'get':
key = op[1]
if key not in cache:
result.append(-1)
continue
cache.move_to_end(key)
result.append(cache[key])
else:
key, value = op[1], op[2]
if key in cache:
cache.move_to_end(key)
cache[key] = value
if len(cache) > capacity:
cache.popitem(last=False)
return resultLRU cache как generic container: edge cases
Какие edge cases появляются, если LRU cache должен хранить любые пользовательские значения?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
Нельзя смешивать “нет значения” и пользовательские значения вроде None. Нужны sentinel, точный API get/put, обработка capacity и стабильная O(1) структура.
Подробный разбор
Если cache хранит любые значения, None может быть нормальным значением. Поэтому get не должен возвращать None как единственный сигнал отсутствия ключа. Используют sentinel object, exception, pair found/value или API с explicit default.
Другие edge cases: capacity 0, повторный put существующего ключа, eviction самого старого элемента, hashability ключей, NaN как ключ/значение, порядок обновления при get и put. Для O(1) обычно нужен dict плюс doubly linked list или OrderedDict-подобная структура.
Что такое p-value
Как объяснить p-value без ошибки “вероятность, что нулевая гипотеза верна”?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
P-value - вероятность при верной H0 получить статистику не менее экстремальную, чем наблюдаемая. Это не вероятность истинности H0.
Подробный разбор
P-value считается в мире, где нулевая гипотеза H0 верна. Он показывает вероятность получить наблюдаемое или более экстремальное значение тестовой статистики. Малый p-value означает, что такие данные плохо согласуются с H0 при выбранной модели теста.
Он не равен вероятности, что H0 верна, и не показывает размер эффекта. Для решения нужны заранее выбранный уровень значимости, мощность/MDE, доверительный интервал и практическая значимость эффекта.
100 экспериментов и ложные открытия
Если провести 100 независимых тестов на уровне значимости 5%, что означает два p-value ниже 0.05?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
При 100 истинных H0 ожидается около 5 ложноположительных результатов. Два значимых результата сами по себе не сильное доказательство без multiple-testing correction.
Подробный разбор
Если все 100 нулевых гипотез истинны и тесты независимы, уровень значимости 5% означает ожидаемо около пяти false positives. Поэтому два p-value ниже 0.05 могут быть полностью совместимы со случайностью, особенно если тесты выбирались постфактум.
Для контроля используют Bonferroni, Holm, Benjamini-Hochberg/FDR или заранее фиксируют primary metrics. В продуктовых экспериментах также важно отделять exploratory analysis от confirmatory test и не выбирать метрику после просмотра результатов.
Base rate и положительный тест
Почему высокая accuracy медицинского теста не означает высокую вероятность болезни после положительного результата?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
Posterior зависит от prevalence. При редкой болезни даже небольшой false positive rate может дать много ложных положительных среди всех positive tests.
Подробный разбор
Нужно считать условную вероятность болезни при положительном тесте через Bayes theorem. В числителе стоит prevalence * sensitivity. В знаменателе - все положительные тесты: true positives плюс false positives среди здоровых.
Если болезнь редкая, здоровых людей намного больше. Даже при хорошей specificity абсолютное число false positives может быть сравнимо или больше true positives. Поэтому “99% accurate” без prevalence, sensitivity и specificity не отвечает на вопрос о posterior probability.
Почему в LSTM явно выделяют time dimension
Какой смысл имеет time dimension в LSTM input и почему порядок шагов важен?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
LSTM обрабатывает последовательность по шагам времени, переносит hidden/cell state и поэтому различает порядок событий. Batch и feature dimensions имеют другой смысл.
Подробный разбор
Для LSTM вход обычно имеет axes batch, time и features. На каждом time step модель получает вектор признаков и обновляет hidden state и cell state. Эти состояния переносят информацию из прошлых шагов, поэтому перестановка времени меняет вычисление.
Разные фреймворки могут ожидать batch-first или time-first формат, но семантика остается той же: time axis задает порядок recurrence. Если перепутать time и feature dimensions, модель будет учить неверную структуру зависимости.
Truncated BPTT для длинных последовательностей
Как обучать LSTM на последовательности длиной 100k шагов, если полный backprop слишком дорогой?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Показать разбор
Короткий ответ
Последовательность режут на окна, hidden state переносят вперед, а граф градиентов периодически detach-ят. Это ограничивает память и длину backprop.
Подробный разбор
Полный backpropagation through time по 100k шагов хранит слишком много активаций и дает нестабильные градиенты. Truncated BPTT обрабатывает последовательность чанками: состояние переносится между окнами, но computational graph обрывается через detach на границе.
Длина окна выбирается как компромисс между дальним контекстом, памятью и стабильностью обучения. Дополнительно используют gradient clipping, padding/masking, bucketing по длинам и иногда архитектуры с attention/temporal convolution, если зависимость слишком дальняя для LSTM.