Динамическое программирование (dynamic programming, ДП) решает задачу через подзадачи меньшего размера и считает каждую подзадачу один раз, запоминая ответы в таблице.
С доски, со ссылкой на «неизвестного автора»: ДП — это когда есть задача, которую мы не умеем решать, и для решения мы её разбиваем на подзадачи, которые тоже не умеем решать.
Зачем нужно
Рекурсивная формула часто сама просится в код: .
Но если подзадачи перекрываются, прямая Рекурсия пересчитывает одни
и те же значения: fib(n − 2) вызывают и fib(n), и fib(n − 1). Число
вызовов растёт экспоненциально. ДП заменяет повторный счёт чтением из
таблицы — время становится полиномиальным.
Как работает
Не из лекции: схема
- Состояние — что хранится в
F[N].- Переход — формула через меньшие состояния (на доске — инвариант ДП).
- База — граничные условия.
- Порядок вычисления — так, чтобы нужное уже было посчитано.
- Ответ — какое состояние отвечает на вопрос.
Переход обычно доказывают разбором по последнему шагу: последний прыжок кузнечика, последняя ступень, последняя монета. Корректность всего решения — индукцией по состояниям.
Три способа на Фибоначчи
, .
fib(n): если n ≤ 2: return 1
return fib(n − 1) + fib(n − 2) // рекурсивный
table[1] = table[2] = 1 // табличный
for i = 3 ... N: table[i] = table[i − 1] + table[i − 2]
a = b = 1 // линейный
for i = 3 ... N: c = a + b; a = b; b = c| Память | Время | Простота | Хранение промежуточных данных | |
|---|---|---|---|---|
| Рекурсивный | ± | − | + | − |
| Табличный | − | ± | ± | + |
| Линейный | + | + | − | − |
Не из лекции
Рекурсивный — времени (вызовов ровно ), табличный — времени и памяти, линейный — времени и памяти. Промежуточный вариант — мемоизация: та же рекурсия, но посчитанные значения запоминаются, и повторный вызов сразу возвращает ответ. Это ДП «сверху вниз», таблица — «снизу вверх».
Одномерная динамика
| Задача | Переход | База |
|---|---|---|
| Кузнечик : число способов | ||
| Кузнечик | , | |
| Лесенка: минимум ударов | , | |
| Панда: максимум пельменей | , | |
| Монеты: минимум монет |
Двумерная динамика
Состояние — пара чисел, таблица двумерная.
- Сочетания: , .
- Разбиения на слагаемые:
F[i][k]— число разбиений с максимальным слагаемым ровно : ; при , при .
Одномерная попытка для разбиений учитывает порядок слагаемых и не даёт ответ. Второе измерение («максимальное слагаемое») фиксирует порядок — слагаемые по невозрастанию.
Тонкости
- ДП — не любое «разбить на подзадачи». У сортировки слиянием и бинарного поиска подзадачи не повторяются: это «разделяй и властвуй». На лекции они названы примерами ДП, см. конспект.
- Рекурсия по формуле ДП без таблицы — тоже экспонента: на доске записано именно так.
- Граничных условий нужно столько, насколько далеко назад заглядывает формула.
- В задачах на число способов : пустой путь — один способ.
- ДП находит оптимум там, где Жадный алгоритм ошибается: он перебирает все варианты последнего шага, а не только лучший на вид.
Частые ошибки
- Писать рекурсию по формуле и не запоминать ответы.
- Забыть граничное условие или задать его неверно ( вместо 1 в подсчёте способов).
- Считать в неправильном порядке — обращаться к ещё не посчитанной ячейке.
- Выбрать состояние, которое не различает нужное: одномерные разбиения считают и дважды.