Программирование Java: динамическое программирование на примере лестницы

человек бежит вверх по лестнице с n ступенями, и может пройти или 1 Шаг, 2 шага, или 3 шага одновременно. Теперь напишите программу, чтобы подсчитать, сколько возможных способов ребенок может пробежать по лестнице.

данный код выглядит следующим образом

public static int countDP(int n, int[] map) {
 if (n<0)
   return 0;
 else if (n==0)
   return 1;
 else if (map[n]>-1)
   return map[n];
 else {
    map[n] = countDP(n-1, map) + countDP(n-2, map) + countDP(n-3, map);
    return map[n]; }
 }

Я знаю C и C++, а не JAVA. Это из книги Интервью о взломе кода. Может ли кто-нибудь объяснить

  1. почему и как она работает функция map здесь? карта вот массив правильно?

  2. Я не вижу никакой строки для сохранения ввода в массив карты, но как бы это вернуло что-то?

  3. У кого-нибудь есть идея C++ или C версии этого кода? Трудно понять этот код. Возможно, не из-за грамматики JAVA, а из-за неявной структуры динамического программирования.

  4. какова будет временная сложность этого алгоритма? Он должен быть меньше O(3^n) ?

Я был бы очень признателен.

Спасибо, ребята!--2-->

5 ответов


хорошо, вот что делает код.

 `if (n<0)`
    `return 0;`

если осталось недостаточно шагов, то не считайте его. Например, если осталось два шага, но пользователь пытается сделать три шага, то это не считается возможной комбинацией.

else if (n==0) return 1;

если количество оставшихся шагов соответствует количеству доступных шагов, которые пользователь пытается предпринять, это возможная комбинация. Итак, возвращать 1, потому что это возможная комбинация и должна быть добавлена к общему количеству допустимых комбинаций.

else if (map[n]>-1) return map[n];

вот динамическая часть программирования. Предположим, что все значения в массиве имеет значение -1. Таким образом, если число больше -1, оно уже было решено, поэтому вместо его разрешения верните общее количество комбинаций из шага N.

`map[n] = countDP(n-1, map) + countDP(n-2, map) + countDP(n-3, map);`

return map[n]; }

наконец, эта часть решает код. Число возможных комбинаций равно числу возможных комбинаций пользователь может получить, если он занимает 1 шаг + число возможных комбинаций пользователь может получить, если он берет 2 шага + количество возможных комбинаций пользователь может получить, если он выполняется в три этапа.

пример, предположим, есть 5 шагов

простой запуск будет выглядеть так:

//The number of solutions from the fifth step

countDp(5) = countDp(4)+countDp(3)+countDp(2);

//Number of solutions from the fourth step

countDP(4) = countDp(3)+countDp(2)+countDp(1);

//Number of solutions from the third step

countDp(3) = countDp(2)+countDp(1)+countDp(0);
//Number of solutions from the second step
countDp(2) = countDp(1)+countDp(0)+countDp(-1);
//Number of solutions from the first step
countDp(1) = countDp(0) + countDp(-1)+countDp(-2);
//Finally, base case
countDp(0) = 1;

countDp(-1)= 0;
countDp(-2)= 0;
countDp(1) = 1+0+0 = 1;
countDp(2) = 1+1+0 = 2;  //Dynamic programming: did not have to resolve for countDp(1), instead looked up the value in map[1]
countDp(3) = 2+1+1 = 4;  //Dynamic programming, did not have to solve for countDp(1), countDp(2), instead looked up value in map[1] and map[2]
countDp(4) = 4+2+1=7 //Dynamic programming, did not have to solve for CountDp(3),CountDp(2), CountDp(1), just looked them up in map[3],map[2],map[1]
countDp(5)=  2+4+7=13 //Dynamic programming, just used map[4]+map[3]+map[2]

почему и как она работает функция map здесь?

книга показывает метод динамического программирования, который называется memoization. Он используется, чтобы избежать вычисления того же числа снова: если элемент не -1, затем он был вычислен снова, и повторное вычисление означало бы тратить много циклов процессора. DP вычисляет значение один раз, а затем возвращает его каждый раз, когда значение необходимо.

карта вот такие права?

правильно, map имеет тип массива.

я не вижу никакой строки для сохранения ввода в массив карт, но как бы он что-то вернул?

это будет задание на третьей строке снизу:

map[n] = countDP(n-1, map) + countDP(n-2, map) + countDP(n-3, map);

у кого-нибудь есть идея C++ или C версии этого кода? Трудно понять этот код. Возможно, не из-за грамматики JAVA, а из-за неявного структура динамического программирования.

правильно, DP и memoization занимают некоторое время, чтобы привыкнуть. Выполните этот алгоритм один раз с помощью бумаги и карандаша для небольшого числа, скажем, 10. Это покажет вам, как оптимальная подструктура ответа помогает этому алгоритму придумать ответ так быстро.

какова будет временная сложность этого алгоритма? Он должен быть меньше O (3^n) ?

абсолютно! Каждый элемент вычисляется ровно один раз, и каждый элемент принимает амортизированный O(1) для вычисления, поэтому общая сложность этого кода O(N). Это может быть контринтуитивно, поскольку вы наблюдаете, как цепочка рекурсивных вызовов вычисляет countDP(K) принимает O(K) рекурсивные вызовы. Однако каждый вызов завершает вычисление K пункты map (обратите внимание, как map это улица с односторонним движением: как только вы установите неотрицательное значение в ячейку, оно навсегда останется неотрицательным, поэтому повторные вычисления то же значение через любой другой путь вызова будет принимать то же самое O(1) времени.


1.) map-это целочисленный массив. Обозначение в Java заключается в том, что map[n] возвращает целочисленное значение в индексе n.

2.) Возвращает целое число, поскольку map[n] возвращает целочисленное значение с индексом n. Единственный раз, когда значение сохраняется в массиве, - это

map[n] = countDP(n-1, map) + countDP(n-2, map) + countDP(n-3, map);

это рекурсивный вызов для поиска суммы шагов путем подсчета всех возможных комбинаций 1 , 2 и 3.

3.)

int countDP(int n, int map[])
{
if (n<0)
    return 0;
else if (n==0)
    return 1;
else if (map[n]>-1)
    return map[n];
else {
    map[n] = countDP(n-1, map) + countDP(n-2, map) + countDP(n-3, map);
    return map[n]; 
}


}

4.) Да сложность была бы намного быстрее, чем O (3^n).


решение JavaScript: (итеративное )

function countPossibleWaysIterative(n) {
  if (n < 0){
    return -1; // check for negative, also might want to check if n is an integer
  } if (n === 0) {
    return 0; // for case with 0 stairs
  } else if (n === 1) {
    return 1; // for case with 1 stairs
  } else if (n === 2) {
    return 2; // for case with 2 stairs
  } else {

    var prev_prev = 1;
    var prev = 2;
    var res = 4; // for case with 3 stairs

    while (n > 3) { // all other cases
      var tmp = prev_prev + prev + res;
      prev_prev = prev;
      prev = res;
      res = tmp;
      n--;
    }
  }
  return res;
}

/**
 * Created by mona on 3/3/16.
 */
import java.util.Hashtable;
public class StairCount {
    /*
     A man is running up a staircase with n steps, and can go either 1 steps, 2 steps,
      or 3 steps at a time. count how many possible ways the child can run the stairs.
     */
    static Hashtable<Integer, Long> ht=new Hashtable<>();

    public static long stairs(int n){
        if (!ht.containsKey(1)){
            ht.put(1, (long) 1);
        }
        if (!ht.containsKey(2)){
            ht.put(2, (long) 2);
        }
        if (!ht.containsKey(3)){
            ht.put(3, (long) 4);
        }

/*
        if (!ht.containsKey(n)){
            ht.put(n, stairs(n-1)+ht.get(1)+stairs(n-2)+ht.get(2)+stairs(n-3)+ht.get(3));
        }
*/
        if (!ht.containsKey(n)){
            ht.put(n, stairs(n-1)+stairs(n-2)+stairs(n-3));
        }
        return ht.get(n);
    }
    public static void main(String[] args){
        System.out.println(stairs(4));

    }
}

//ответ на 4 на 14 и 5 составляет 27. Для строки комментария. Может кто-нибудь прокомментировать, почему мой мыслительный процесс был неправильным?