Все темы
58

Когда одна и та же работа делается тысячу раз

Tier 1Закрыто

Где применяется

1. Задача

Ты написал простую рекурсию. Она даёт правильный ответ. Потом запускаешь её на входе чуть побольше, и она думает десять секунд. Ошибки в ней нет. Рекурсия просто снова и снова решает одну и ту же маленькую задачу.

Возьми числа Фибоначчи: fib(n) = fib(n-1) + fib(n-2). Чтобы получить fib(30), вызовы разворачиваются в огромное дерево: всего 2 692 537 вызовов. Один только fib(5) в нём считается 121 393 раза. Каждая ветка заново делает работу, которую уже сделала другая ветка. Прибавь к n всего 2, и вызовов станет примерно в 2,6 раза больше.

Время уходит на повторы. Если отвечать на каждый отдельный вопрос один раз и запоминать ответ, всё будет работать быстро.

Этот шаг из платной части

Бесплатные шаги в начале курса (их 33) открыты всем: урок, задача и тесты. Этот шаг открывается с полным доступом. Начни с бесплатного аккаунта и бесплатных шагов.

Завести бесплатный аккаунтПосмотреть, что входит
Числа Фибоначчи с таблицей memo
fib(5)
стек вызовов
пусто
memo: n → fib(n), уже посчитанные
n=2n=3n=4n=5
memo
calls = 0cache hits = 0

Считаем fib(5). Обычная рекурсия много раз просит одно и то же fib(n). memo запоминает каждый ответ, как только он найден.

Гонка скорости
fib(17) простой рекурсией и той же рекурсией с запоминанием

Столбики: сколько раз fib(i) действительно вычислялось, i от 0 до 17. Вызов, на который ответила память, ничего не вычисляет, и его столбик не растёт.

Одна операция: один вызов, ответ из памяти тоже считается за один. За каждый кадр все дорожки делают одинаковое число операций, поэтому медленная отстаёт.

Простая рекурсия O(2ⁿ)
операций: 0
Рекурсия с запоминанием O(n)
операций: 0

 

А если чисел больше

Операции для n чисел и примерное время при 100 миллионах простых операций в секунду. Это оценка по тому же счёту, а не замер.

n = 20
Простая рекурсия21,9 тыс. · меньше 1 мс
Рекурсия с запоминанием39 · меньше 1 мс
n = 40
Простая рекурсия331,2 млн · 3,3 с
Рекурсия с запоминанием79 · меньше 1 мс
n = 60
Простая рекурсия5 трлн · 14 ч
Рекурсия с запоминанием119 · меньше 1 мс

Как узнать

Рекурсия с мемоизацией (Recursion with memoization)

Эти слова в условии значат этот паттерн

  • рекурсивные вызовы повторяют одни и те же аргументы (пересекающиеся подзадачи)
  • обычная рекурсия работает верно, но слишком медленно / экспоненциальный взрыв
  • мало различных состояний, хотя дерево рекурсии огромное
  • закешировать результат (i, ...) перед возвратом
  • формулировка вроде Фибоначчи / подъём по лестнице / путь по сетке с намёком на кеш

Похоже, но не то

  • Рекурсия (Recursion)

    Обычная рекурсия каждый раз пересчитывает повторный аргумент. Кеш это вся разница.

  • Динамическое программирование (1-D) (Dynamic programming (1-D))

    Динамика снизу заполняет таблицу в заданном порядке. Здесь она заполняется по требованию, сверху.

  • Хеш-множество / словарь (Hash set / map)

    Кеш обычно хранят в словаре. Эта страница про то, когда его вообще заводить.

  • Бэктрекинг (Backtracking)

    Бэктрекинг обходит пути, которые все разные, поэтому ничего не повторяется. Кеш там хранит ключи, которых больше не спросят.

Стоимость это число различных аргументов на работу одного вызова. Кеш в 1e6 ключей нормален.

На LeetCode

Задачи на подсчёт, где простая рекурсия много раз задаёт один и тот же вопрос, например сколько способов подняться по лестнице или пройти по сетке. Сохраняй каждый ответ в мемо с первого раза и используй повторно.

Мы используем необходимые куки для работы сайта (вход и язык). Если ты согласишься, мы также загрузим Google Analytics, чтобы видеть, какие страницы читают, и Google reCAPTCHA для защиты форм обратной связи и сообщений об ошибке от спама. Политика конфиденциальности