Динамическое программирование
Что такое динамическое программирование?
Это метод оптимизации, который решает сложные задачи, разбивая их на более простые подзадачи.
Основная идея: если мы можем решить большую проблему, используя решения меньших подпроблем, и эти подпроблемы повторяются, мы можем сохранить их решения, чтобы не вычислять их снова.
Ключевые концепции
a. Оптимальная подструктура:
Оптимальное решение большой проблемы состоит из оптимальных решений её подпроблем.
Пример: кратчайший путь от A до C через B - это комбинация кратчайших путей A→B и B→C.
b. Перекрывающиеся подпроблемы:
Одни и те же подпроблемы встречаются многократно. Вместо повторного решения, мы сохраняем результаты в структуре данных (обычно массив или хеш-таблица).
Подходы к ДП
a. Сверху вниз (Мемоизация):
Начинаем с основной проблемы и рекурсивно разбиваем её на подпроблемы.
Используем кэш (обычно массив или Map) для хранения результатов.
Перед вычислением проверяем, нет ли результата в кэше.
b. Снизу вверх (Табличный):
Начинаем с самых маленьких подпроблем.
Итеративно строим решения больших проблем из меньших.
Заполняем таблицу (массив) в определенном порядке.
Термины "сверху вниз" и "снизу вверх" относятся только к тому, как вы решаете реализовать свой алгоритм. По сути, между этими двумя подходами нет никакой разницы. Любую реализацию сверху вниз можно реализовать снизу вверх и наоборот. То, что определяет алгоритм динамического программирования (ДП) - это базовые случаи и рекуррентное соотношение
Пример кода без ДП
var fibonacci = function (n) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
};

Пример кода с ДП - мемоизация
var fibonacci = function (n) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
if (memo.has(n)) {
return memo.get(n);
}
memo.set(n, fibonacci(n - 1) + fibonacci(n - 2));
return memo.get(n);
};
let memo = new Map();
Шаги для решения задач ДП
a. Определить подпроблемы и их структуру.
b. Написать рекуррентное соотношение между п одпроблемами.
c. Определить базовые случаи.
d. Выбрать подход (сверху вниз или снизу вверх).
e. Реализовать решение.
Преимущества
- Эффективно решает задачи с повторяющимися вычислениями.
- Часто превращает экспоненциальную сложность в полиномиальную.
Недостатки
- Требует дополнительной памяти для хранения промежуточных результатов.
- Не всегда интуитивно понятно, особенно при первом знакомстве.
Когда применять
Задачи, которые следует решать с помощью динамического программирования (ДП), обычно имеют две основные характеристики: