Одномерная DP (1D DP)
1D DP (одномерное динамическое программирование) — это метод решения задач, где состояние описывается одной переменной, а оптимальное решение строится на основе ранее вычисленных подзадач.
Подробное описание
Постановка задачи: дана последовательность или числовая прямая, на которой требуется найти оптимальное значение (максимум, минимум, количество способов) с учётом того, что выбор на текущем шаге зависит от результатов предыдущих шагов.
Входные данные: длина последовательности n, правила перехода между состояниями (обычно задаются рекуррентным соотношением).
Выходные данные: искомое значение (число, булево значение или набор шагов).
Ключевая идея: разбить задачу на подзадачи, каждая из которых решается один раз; результат сохраняется в таблице (DP-массиве) и переиспользуется, избегая повторных вычислений.
Основные принципы
- Определение состояния: выбирается параметр, полностью описывающий подзадачу (обычно индекс в массиве).
- Рекуррентное соотношение: формулируется, как решение для текущего состояния зависит от предыдущих.
- Базовый случай: определяются тривиальные случаи, с которых начинается заполнение таблицы.
- Порядок заполнения: выбирается направление вычислений (обычно слева направо).
- Оптимизация памяти: часто можно хранить только последние несколько значений вместо всей таблицы.
Математическая формулировка
Для классической задачи чисел Фибоначчи рекуррентное соотношение имеет вид:
Где: - \(F(n)\) — значение n-го числа Фибоначчи - \(F(0) = 0\), \(F(1) = 1\) — базовые случаи
Для задачи о кузнечике количество способов добраться до позиции \(n\):
Где: - \(dp[n]\) — количество способов добраться до позиции \(n\) - \(dp[1] = 1\), \(dp[2] = 2\) — базовые случаи
Блок-схема
flowchart TD
A[Начало] --> B[Определить состояние]
B --> C[Задать рекуррентное соотношение]
C --> D[Определить базовые случаи]
D --> E[Выбрать порядок заполнения]
E --> F[Заполнить DP-таблицу]
F --> G[Вернуть результат]
Пример реализации на Python
from typing import List
def fibonacci(n: int) -> int:
# Базовые случаи
if n == 0:
return 0
if n == 1:
return 1
# Инициализируем массив для хранения промежуточных результатов
dp: List[int] = [0] * (n + 1)
dp[0] = 0 # F(0)
dp[1] = 1 # F(1)
# Заполняем массив по порядку слева направо
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2] # Рекуррентное соотношение
return dp[n]
def count_ways(n: int) -> int:
# Количество способов добраться до позиции n
if n == 1:
return 1
if n == 2:
return 2 # 1+1 или 2
dp: List[int] = [0] * (n + 1)
dp[1] = 1
dp[2] = 2
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
def fibonacci_optimized(n: int) -> int:
# Версия с экономией памяти: O(1) вместо O(n)
if n == 0:
return 0
if n == 1:
return 1
prev_prev = 0 # F(n-2)
prev = 1 # F(n-1)
for _ in range(2, n + 1):
current = prev + prev_prev
prev_prev, prev = prev, current
return prev
if __name__ == "__main__":
print(f"fibonacci(10) = {fibonacci(10)}") # 55
print(f"fibonacci_optimized(10) = {fibonacci_optimized(10)}") # 55
print(f"count_ways(5) = {count_ways(5)}") # 8
Достоинства и недостатки
Достоинства:
- Эффективность — каждое состояние вычисляется один раз, что даёт временную сложность \(O(n)\).
- Простота реализации — для одномерных задач достаточно одного массива и цикла.
- Оптимизация памяти — во многих случаях можно свести пространственную сложность к \(O(1)\).
Недостатки:
- Ограниченная размерность — метод применим только когда состояние описывается одной переменной; многомерные задачи требуют более сложных подходов.
- Необходимость рекуррентного соотношения — для ряда задач его трудно или невозможно сформулировать.
- Фиксированный порядок вычислений — не все задачи допускают простое заполнение слева направо.
Области применения
- Биотехнологии и медицина (анализ последовательностей ДНК/РНК, поиск паттернов)
- Экономика и финансы (прогнозирование временных рядов, выявление трендов)
- Обработка текста (поиск повторяющихся фрагментов, исправление ошибок)
- Компьютерное зрение (сопоставление одномерных сигналов, например, в спектроскопии)
- Логистика (оптимизация маршрутов на основе исторических данных перемещений)