Количество точек пересечения двух окружностей
Write a function that returns how many intersection points two circles have. Cover separated circles, external and internal tangency, one circle inside another, and coincident circles.
Решение прямо на странице
Напишите код, запустите проверки и только потом открывайте разбор.
Нажмите «Запустить проверки» или Ctrl+Enter.
Подсказки
- Два случая без пересечения
Окружности могут быть внешне разнесены или одна окружность может лежать строго внутри другой.
- Касание — это граница
Проверяйте равенство r1 + r2 и abs(r1 - r2) с epsilon.
Идея решения
Пусть d — расстояние между центрами окружностей.
Пересечений нет, если центры слишком далеко друг от друга: d > r1 + r2. Ещё один случай без пересечений — одна окружность строго внутри другой: d < abs(r1 - r2). Касание происходит на границах этих условий. Во всех остальных случаях окружности пересекаются в двух точках.
Случай совпадающих окружностей нужно разобрать до обычных сравнений: одинаковый центр и одинаковый радиус дают бесконечно много общих точек. Для float-сравнений используйте небольшой epsilon вместо точного равенства.
Эталонный код
import math
def count_circle_intersections(
x1: float,
y1: float,
r1: float,
x2: float,
y2: float,
r2: float,
) -> int:
eps = 1e-9
d = math.hypot(x1 - x2, y1 - y2)
if d <= eps:
return -1 if abs(r1 - r2) <= eps else 0
outer = r1 + r2
inner = abs(r1 - r2)
if d > outer + eps:
return 0
if d < inner - eps:
return 0
if abs(d - outer) <= eps or abs(d - inner) <= eps:
return 1
return 2Точка внутри замкнутого треугольника
Write a function that checks whether a point lies inside or on the boundary of a triangle. Use a robust geometric criterion and handle boundary points deliberately.
Решение прямо на странице
Напишите код, запустите проверки и только потом открывайте разбор.
Нажмите «Запустить проверки» или Ctrl+Enter.
Подсказки
- Используйте ориентацию
Двумерное векторное произведение даёт знаковую площадь и показывает, с какой стороны направленного ребра лежит точка.
- Замкнутый треугольник
Точки на границе должны проходить, поэтому используйте нестрогие неравенства с небольшим epsilon.
Идея решения
Используем знак двумерного векторного произведения. Если точка лежит внутри выпуклого многоугольника, то при фиксированном направлении обхода она находится с одной стороны от каждого ребра.
Для треугольника abc считаем orient(a, b, p), orient(b, c, p) и orient(c, a, p). Точка лежит внутри замкнутого треугольника, если все значения неотрицательные или все неположительные. Нули покрывают точки на границе.
Перед этим нужно отсеять вырожденный треугольник через orient(a, b, c).
Эталонный код
def point_in_triangle(
p: tuple[float, float],
a: tuple[float, float],
b: tuple[float, float],
c: tuple[float, float],
) -> bool:
eps = 1e-9
def orient(u, v, w):
return (v[0] - u[0]) * (w[1] - u[1]) - (v[1] - u[1]) * (w[0] - u[0])
if abs(orient(a, b, c)) <= eps:
return False
values = [orient(a, b, p), orient(b, c, p), orient(c, a, p)]
return all(value >= -eps for value in values) or all(value <= eps for value in values)Количество нулей в конце n!
Write a function that returns the number of trailing zeroes in n factorial without relying on huge factorial values.
Решение прямо на странице
Напишите код, запустите проверки и только потом открывайте разбор.
Нажмите «Запустить проверки» или Ctrl+Enter.
Подсказки
- Не вычисляйте факториал
Само число растёт слишком быстро; важны только его простые множители.
- Кратные 25 считаются дважды
25 даёт два множителя 5, 125 даёт три и так далее.
Идея решения
Ноль в конце появляется из множителя 10 = 2 * 5. В n! множителей 2 больше, чем множителей 5, поэтому ответ равен количеству множителей 5.
Считаем кратные 5, затем кратные 25, затем 125 и так далее. Так учитываются числа, которые дают несколько множителей 5.
Эталонный код
def trailing_zeroes_factorial(n: int) -> int:
result = 0
divisor = 5
while divisor <= n:
result += n // divisor
divisor *= 5
return resultВопрос
Does Python int overflow? How can you roughly estimate how much memory n! needs without computing the factorial?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Короткий ответ
Python ints are arbitrary precision, so they do not overflow like fixed 64-bit ints. Memory grows with bit length, roughly log2(n!), which can be estimated by Stirling.
Подробный разбор
Python int is arbitrary precision. Arithmetic does not overflow at 32 or 64 bits; instead, the object allocates more machine words as the value grows. Eventually you can still run out of memory or time.
To estimate memory for n!, estimate the number of bits rather than the numeric value itself. The bit length is floor(log2(n!)) + 1. Using Stirling, log2(n!) is approximately n log2(n) - n log2(e) plus a smaller term from sqrt(2 pi n). Divide by 8 for bytes and remember that Python object overhead means the actual memory is higher.
This is the key reasoning move in the interview: switch from the magnitude of n! to the logarithm of n!, because storage is proportional to digits or bits.
Типичные ошибки
- Say Python int can never fail.
- Estimate bytes from the decimal value instead of digit count.
- Forget interpreter object overhead.
Как сказать на собеседовании
- Use bit_length as the practical Python API.
- Use Stirling only for rough order-of-magnitude reasoning.
Итераторы, генераторы и StopIteration в Python
Что происходит внутри Python for-loop, чем iterator отличается от generator и зачем нужен StopIteration?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Короткий ответ
for-loop вызывает iter(obj), затем next(iterator), пока iterator не поднимет StopIteration. Генератор - это удобный способ создать iterator через функцию с yield.
Подробный разбор
В for-loop Python сначала получает iterator через iter(obj). Потом он многократно вызывает next(iterator). Когда значения закончились, iterator поднимает StopIteration, и цикл завершается нормально.
Iterator - это объект с протоколом __iter__ и __next__: __iter__ возвращает iterator, а __next__ возвращает следующее значение или поднимает StopIteration. Generator - частный и очень удобный способ создать iterator: функция с yield приостанавливается и сохраняет локальное состояние между вызовами next.
Для обычного ответа на собеседовании этого достаточно: iter, next, StopIteration и yield. Методы generator вроде send/throw/close можно упомянуть только как дополнительный факт, если интервьюер уводит вопрос глубже.
Типичные ошибки
- Сказать, что generator возвращает готовый list.
- Забыть StopIteration.
- Думать, что __iter__ и __next__ бывают только у built-in объектов.
Как сказать на собеседовании
- Объясните for-loop словами через iter и next.
- Скажите, что yield сохраняет состояние функции.
Вопрос про production ML
What is a Python context manager, what do __enter__ and __exit__ do, and why not just wait for garbage collection?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Короткий ответ
A context manager wraps setup and cleanup around a block. __enter__ prepares the resource, __exit__ runs even on exceptions, and this is safer than waiting for GC to close scarce OS resources.
Подробный разбор
The with statement is syntax for entering and leaving a managed context. Python calls __enter__ before the block and __exit__ after the block, including when the block exits through an exception. Files, locks, transactions, sockets and temporary settings are common examples.
The reason is deterministic cleanup. Garbage collection is not a good resource-management policy for file descriptors, sockets, locks or database connections. These are external system resources with limits and side effects. Waiting until an object is eventually collected can keep handles open too long, leak locks, hold transactions or exhaust descriptors.
In ML code, the same pattern appears in things such as torch.no_grad or temporary precision/autocast contexts: the context manager makes state changes scoped and reversible.
Типичные ошибки
- Describe only files and miss the general protocol.
- Assume GC cleanup is immediate and portable.
- Forget exception behavior.
Как сказать на собеседовании
- Say deterministic cleanup explicitly.
- Give one OS resource example and one ML/Python example.
Вопрос
How does @dataclass reduce boilerplate, what does frozen=True do, and how do descriptors or properties relate to attribute access?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Короткий ответ
@dataclass generates boilerplate such as __init__, __repr__ and comparisons from annotations. frozen=True prevents normal assignment after initialization. Descriptors customize attribute get/set/delete behavior.
Подробный разбор
A dataclass is a class decorator that reads annotated fields and generates common methods, usually including __init__, __repr__ and equality methods. It is a convenience layer over an ordinary Python class, not a new object model.
With frozen=True, the generated class blocks normal attribute reassignment after construction. It is useful for value objects such as points or configuration records. It is not the same as deep immutability: mutable values stored inside can still mutate unless you choose immutable field types.
Descriptors are objects that define methods such as __get__, __set__ and __delete__. Python's attribute access machinery calls them to customize reads and writes. property is a common descriptor-based API. The connection to the interview is that high-level conveniences such as dataclasses and properties ultimately sit on top of Python's attribute model.
Типичные ошибки
- Think type hints enforce runtime types by default.
- Treat frozen dataclasses as deeply immutable.
- Confuse class attributes with instance attributes.
Как сказать на собеседовании
- Name two methods dataclass generates.
- Use property as the simplest descriptor example.
Декораторы, functools.wraps и ленивый импорт
Как написать декоратор для логирования вызова, зачем нужен functools.wraps и как лениво импортировать модуль внутри wrapper-а?
Сначала проговорите ответ вслух или тезисами.
Формулы, план решения, риски и примеры.
Откройте разбор только после своей попытки.
Короткий ответ
Декоратор принимает функцию и возвращает wrapper. wrapper делает дополнительную работу, вызывает исходную функцию и возвращает результат. functools.wraps сохраняет имя, docstring и __wrapped__, а ленивый импорт можно сделать через importlib.import_module внутри wrapper-а.
Подробный разбор
Базовый декоратор принимает функцию и возвращает другой callable. Внутренний wrapper принимает *args и **kwargs, может залогировать вход, вызвать исходную функцию, залогировать результат и вернуть его.
functools.wraps(func) нужен, чтобы wrapper не ломал метаданные: __name__, __doc__, annotations и __wrapped__. Это важно для отладки, introspection, тестов и инструментов.
Если у декоратора есть аргументы, появляется один дополнительный уровень функции: factory получает конфигурацию, затем decorator получает исходную функцию, затем wrapper получает runtime-аргументы. Ленивый импорт можно делать внутри wrapper-а через importlib.import_module(name), когда модуль реально нужен; при необходимости модуль кешируют в closure. Важно понимать: ошибка импорта тогда случится во время вызова, а не при старте программы.
Типичные ошибки
- Забыть вернуть wrapper.
- Забыть *args и **kwargs.
- Не использовать functools.wraps и сломать introspection.
Как сказать на собеседовании
- Сначала объясните обычный wrapper, потом decorator with args.
- Про ленивый импорт говорите как про дополнительный нюанс, а не ядро ответа.