Несколько лет назад мне было трудно понять, как работает концепция динамического программирования (DP). Здесь я хотел бы поделиться тем, как я изучил DP, начав с рекурсии.

Рекурсия

Первый шаг: разбейте проблему на подзадачи.
Второй шаг: остановите подзадачи (базовый случай).

Пример: Допустим, как мы вычисляем 1+2+3+..+n (рекурсия)?

Пусть sum(n) = (1+2+…+n)
Первый шаг: sum(n)= sum(n-1) + n
Второй шаг: sum(1) = 1

// Recursion
int sum(int n){
   if(n==1) return 1;  // second step
   return sum(n-1) + n; // first step
}

Динамическое программирование (DP)

Как насчет написать в форме DP?

Первый шаг: Начните с подзадач (базовый вариант).
Второй шаг: Объедините подзадачи, чтобы решить большую проблему.

Разберем тот же вопрос…
Первый шаг: sum[1] = 1
Второй шаг: sum[n] = sum[n-1]+n;

// DP
int sum[] = new int[n+1];
sum[1] = 1;                 // first step
for(int i=2;i<=n;i++)       
   sum[n] = sum[n-1] + n;   // second step

Давайте попробуем другой пример: последовательности Фибоначчи.
0, 1, 1 ,2 ,3 ,5 ,8 ,…
Как мы можем найти n член числа Фибоначчи?

Пусть fib(n) — это n-й член числа Фибоначчи.
Давайте программировать!

// Recursion
int fib(int n){
   if(n==0||n==1) return n;    // second step
   return fib(n-1) + fib(n-2); // first step
}

Как насчет ДП?

// DP
int fib[] = new int[n];
fib[0] = 0;                      // first step
fib[1] = 1; 
for(int i=2;i<=n;i++)
   fib[i] = fib[i-1] + fib[i-2]; // second step;

Различия между рекурсией и DP?

  • DP использует массив для хранения ответа на подзадачу, а рекурсия — нет.
  • DP быстрее вычисляет результат при вычислении числа Фибоначчи.

Давайте попробуем задать более сложный вопрос!

Вопрос: (Проблема с раздачей монет)
Если у вас неограниченное количество жетонов монет = {1,3,5}. Выясните, сколькими способами (без учета порядка) можно составить его, если общая стоимость монет (монет) равна n.

Для n = 4, 4 = 1+1+1+1, 4 = 1+3
путь(4) = 2
Примечание: путь(0) = 1, потому что если вы не использовали любой жетон монеты, тогда сумма монет равна 0. Есть и один способ!

// Recursion
int way(int n,int index,int token[]){
   if(n==0) return 1;                       // second step
   int total = 0;
   for(int i=index;i<token.length;i++)
      if(n-token[i]>=0)
         total += way(n-token[i],i,token);  // first step
   return total;
}
// int token[] = {1,3,5};
// way(4,0,token) = 2
// way(5000,0,token) = 834834 // very slow

Это может быть слишком медленно, когда значение n становится больше. Попробуем ДП!

// DP
int token[] = {1,3,5};
int way[] = new int[n+1];
way[0] = 1;                      // first step
for(int i=0;i<token.length;i++)
   for(int j=token[i];j<=n;j++)
      way[j] += way[j-token[i]]; // second step
// way[4] = 2
// way[5000] = 834834

Вот как я понимаю DP, надеюсь, это сработает и для вас.