Что такое рекурсия и мемоизация?
- Почему рекурсивные числа Фибоначчи считаются так медленно?
- Что такое переполнение стека при рекурсии?
- Что такое хвостовая рекурсия?
Рекурсия — функция вызывает саму себя для меньшей задачи и останавливается на базовом случае; мемоизация — кэш уже посчитанных результатов.
- Наивный рекурсивный Фибоначчи — O(2ⁿ): одни и те же значения пересчитываются экспоненциально много раз
- С мемоизацией — O(n): каждое значение считается один раз
А ещё Каждый вызов занимает место в стеке: глубокая рекурсия → переполнение стека
Подробно
Рекурсия удобна, когда задача сводится к такой же задаче меньшего размера: обход дерева, «разделяй и властвуй», перебор. Обязательные части:
- базовый случай — когда ответ известен без рекурсии;
- шаг — сведение к меньшей задаче, который гарантированно приближает к базовому случаю.
Каждый вызов кладёт кадр в стек вызовов (параметры, локальные переменные, адрес возврата). При слишком большой глубине стек переполняется: RecursionError в Python (лимит около 1000 по умолчанию), StackOverflowError в Java. Тогда рекурсию переписывают на цикл с явным стеком.
Проблема повторных вычислений. fib(n) = fib(n-1) + fib(n-2) порождает дерево вызовов, где fib(n-2) считается дважды, fib(n-3) — трижды и так далее: число вызовов растёт экспоненциально, примерно как 1,6ⁿ.
Мемоизация — запоминать результат для каждого аргумента и при повторном вызове брать из кэша. Дерево вызовов «схлопывается» в цепочку: O(n) времени и O(n) памяти. Это подход «сверху вниз» к динамическому программированию.
Хвостовая рекурсия — рекурсивный вызов последний в функции. Некоторые языки (Scheme, Scala с аннотацией) превращают её в цикл без роста стека; Python и Java так не делают.
Пример
from functools import lru_cache
def fib_slow(n): # O(2^n)
return n if n < 2 else fib_slow(n - 1) + fib_slow(n - 2)
@lru_cache(maxsize=None)
def fib_fast(n): # O(n): каждое значение — один раз
return n if n < 2 else fib_fast(n - 1) + fib_fast(n - 2)
fib_fast(90) # мгновенно; fib_slow(90) не дождатьсяЧто хотят услышать
- Базовый случай и шаг рекурсии.
- Стек вызовов и риск переполнения.
- Почему наивный Фибоначчи экспоненциальный.
- Мемоизация и как она меняет сложность.
Частые ошибки
- Забыть базовый случай или шаг, который к нему не приближает.
- Не учитывать память стека вызовов в оценке сложности.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Наивный рекурсивный Фибоначчи работает за O(n)
- ✗Рекурсия без базового случая завершается сама по таймауту
- ✗Мемоизация увеличивает число вычислений, но экономит память
- ✓Вызов себя до базового случая; мемоизация кэширует результаты
Что спросят дальше
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.