Программирование 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. Это из книги Интервью о взломе кода. Может ли кто-нибудь объяснить
почему и как она работает функция map здесь? карта вот массив правильно?
Я не вижу никакой строки для сохранения ввода в массив карты, но как бы это вернуло что-то?
У кого-нибудь есть идея C++ или C версии этого кода? Трудно понять этот код. Возможно, не из-за грамматики JAVA, а из-за неявной структуры динамического программирования.
какова будет временная сложность этого алгоритма? Он должен быть меньше 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. Для строки комментария. Может кто-нибудь прокомментировать, почему мой мыслительный процесс был неправильным?