К обычному разбору
Тренировка по собеседованиюСобеседованиеNavio2025-08-14

Navio: Алгоритмическая секция

Идите сверху вниз: сначала попробуйте сами, затем откройте разбор. Если шаг с кодом, пишите решение прямо здесь и запускайте проверки на странице.

Шагов
8
Вопросов
5
Задач
3
1ЗадачаMedium

Количество точек пересечения двух окружностей

Условие

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
Сложность
Время: O(1). Память: O(1).
Решение считает расстояние между центрами и сравнивает его с суммой и разностью радиусов.
2ЗадачаMedium

Точка внутри замкнутого треугольника

Условие

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)
Сложность
Время: O(1). Память: O(1).
Функция считает три ориентированные площади и проверяет согласованность их знаков.
3ЗадачаEasy

Количество нулей в конце 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
Сложность
Время: O(log n). Память: O(1).
Цикл последовательно делит n на 5 и добавляет целую часть частного.
4Вопрос10 мин

Вопрос

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.
5Вопрос10 мин

Вопрос

What happens under the hood in a Python for-loop? How do iterators and generators differ, and what is StopIteration?

Ответьте без подсказки

Сначала проговорите ответ вслух или тезисами.

Запишите черновик

Формулы, план решения, риски и примеры.

Сравните с разбором

Откройте разбор только после своей попытки.

Показать разбор

Короткий ответ

A for-loop calls iter(obj), then repeatedly calls next(iterator) until StopIteration. Generators are iterator objects produced by functions containing yield and have extra coroutine-style methods.

Подробный разбор

In a for-loop, Python first obtains an iterator by calling iter(obj). Then it repeatedly calls next(iterator). When the iterator has no more values, it raises StopIteration, and the loop exits normally.

An iterator is any object implementing the iterator protocol: __iter__ returns the iterator, and __next__ returns the next value or raises StopIteration. A generator is a convenient way to create such an object with a function that uses yield. Each yield pauses the function and preserves its local state until the next call.

Generators are also more than just a syntax shortcut. Generator objects support methods such as send, throw and close, which is why they can be used as coroutine building blocks. In ordinary interview answers, the important baseline is still the iter/next/StopIteration protocol.

Типичные ошибки

  • Say a generator returns a full list.
  • Forget StopIteration.
  • Assume __iter__ and __next__ are only for built-in objects.

Как сказать на собеседовании

  • Write the desugared while-loop in words.
  • Mention yield preserves state between next calls.
6Вопрос8 мин

Вопрос про 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.
7Вопрос10 мин

Вопрос

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.
8Вопрос14 мин

Вопрос про production ML

Write and explain a function decorator that logs calls. What does functools.wraps preserve? How would a decorator with arguments lazily import modules only when the function is called?

Ответьте без подсказки

Сначала проговорите ответ вслух или тезисами.

Запишите черновик

Формулы, план решения, риски и примеры.

Сравните с разбором

Откройте разбор только после своей попытки.

Показать разбор

Короткий ответ

A decorator returns a wrapper around the original function. functools.wraps preserves metadata such as __name__ and __doc__. A decorator with arguments adds one more closure layer and can import modules inside the wrapper.

Подробный разбор

A basic decorator accepts a function and returns another callable. The wrapper receives *args and **kwargs, can log inputs, calls the original function, logs the result, and returns it. functools.wraps(func) should decorate the wrapper so tooling, debugging and introspection still see the original function name, docstring, annotations and __wrapped__ link.

A decorator with arguments has three layers: the outer factory receives configuration, the middle decorator receives the function, and the inner wrapper receives runtime arguments. For lazy imports, the wrapper can call importlib.import_module(name) only at invocation time, optionally cache the module objects in the closure, and then call the original function.

The production nuance is that lazy import changes error timing and can hide import failures until runtime. Use it when startup cost matters, and make the behavior explicit.

Типичные ошибки

  • Forget to return the wrapper.
  • Forget *args and **kwargs.
  • Skip functools.wraps and break introspection.

Как сказать на собеседовании

  • Draw the three layers for a decorator with arguments.
  • Mention importlib.import_module for dynamic import by string name.