Что такое динамическое программирование?
- Чем динамическое программирование отличается от жадного алгоритма?
- Как решить задачу о рюкзаке?
- Что такое подход сверху вниз и снизу вверх?
Динамическое программирование — решать задачу через ответы на перекрывающиеся подзадачи, считая каждую один раз и сохраняя в таблицу.
- Условия: оптимальная подструктура и перекрывающиеся подзадачи
- Сверху вниз — рекурсия с мемоизацией; снизу вверх — заполнение таблицы циклом
А ещё Рецепт: определить состояние, переход, базу и порядок вычисления
Подробно
Подход применим, когда:
- оптимальная подструктура — оптимальный ответ строится из оптимальных ответов на подзадачи;
- перекрывающиеся подзадачи — одни и те же подзадачи встречаются многократно (в отличие от «разделяй и властвуй», где части независимы).
Как решать:
- Состояние — что описывает подзадачу:
dp[i]— ответ для первых i элементов,dp[i][j]— для префиксов двух строк. - Переход — как ответ выражается через меньшие подзадачи.
- База — ответы для самых маленьких подзадач.
- Порядок — так, чтобы нужные значения были посчитаны раньше.
Два способа реализации:
- сверху вниз — рекурсия с мемоизацией; проще написать, считаются только нужные состояния;
- снизу вверх — цикл заполняет таблицу от маленьких подзадач к большим; нет риска переполнения стека, часто можно хранить только последнюю строку таблицы и экономить память.
Классические задачи: числа Фибоначчи, число способов подняться по лестнице, рюкзак 0/1, наибольшая общая подпоследовательность (LCS), расстояние редактирования (Левенштейна), размен монет.
Отличие от жадного алгоритма: жадный делает локально лучший выбор и не пересматривает его — быстро, но верно только для задач со специальной структурой (размен монет 1/5/10 — да, 1/3/4 — нет). Динамическое программирование перебирает все варианты через подзадачи и гарантирует оптимум.
Пример
def knapsack(items, capacity):
"""items: [(вес, ценность)] — максимальная ценность в рюкзаке вместимостью capacity."""
dp = [0] * (capacity + 1) # dp[c] — лучшая ценность при вместимости c
for weight, value in items:
for c in range(capacity, weight - 1, -1): # с конца, чтобы взять предмет один раз
dp[c] = max(dp[c], dp[c - weight] + value)
return dp[capacity]
knapsack([(1, 1), (3, 4), (4, 5), (5, 7)], 7) # 9: предметы весом 3 и 4Что хотят услышать
- Два условия применимости.
- Рецепт: состояние, переход, база, порядок.
- Сверху вниз против снизу вверх.
- Классическая задача с решением и отличие от жадного подхода.
Частые ошибки
- Не сформулировать состояние явно и запутаться в индексах.
- В рюкзаке 0/1 идти по вместимости слева направо — предмет возьмётся многократно.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Это любой рекурсивный алгоритм
- ✗Перебор всех вариантов решения с отсечением заведомо плохих ветвей; подзадачи считаются заново каждый раз, зато не тратится память на таблицу
- ✓Решение через перекрывающиеся подзадачи, каждая считается один раз и сохраняется; сверху вниз с мемоизацией или снизу вверх таблицей
- ✗Динамическое программирование требует отсутствия оптимальной подструктуры
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.