Задача динамического программирования на монетах
Рассмотрим следующую задачу
Дано бесконечное число пятаков (5 центов) и Пенни(1 цент). Напишите код для вычисления количества способов представления n центов.
Мой код
def coins(n):
if (n < 0):
return 0
elif (n == 0):
return 1
else:
if (cnt_lst[n-1] == 0):
cnt_lst[n-1] = coins(n-1) + coins(n-5)
return cnt_lst[n-1]
if __name__ == "__main__":
cnt = int(input())
cnt_lst = [0] * cnt #Memiozation
ret = coins(cnt)
print(ret)
Выше оценочность подсчета повторяющихся паттернов более одного (очевидно, я не проверяю их явно).
[5,1,1,1,1,1] [1,5,1,1,1,1] [1,1,5,1,1,1], etc
Ведение другого списка, содержащего ранее замеченные паттерны, потребует много памяти по мере роста n
. Какой еще подход мы можем использовать для преодоления этой проблемы?
2 ответа:
Вместо использования одномерного списка можно использовать двумерный массив, где одно измерение-это сумма, а второе измерение-самая крупная монета из доступных.
Предположим, что
C
- это ваш список значений монет в порядке возрастания (в вашем примереC = [1, 5]
). Тогда пустьA[i][j]
- это число способов представления ценностиi
с помощью монет0
черезj
.Мы знаем, что для любого
Теперь предположим, что мы хотим найтиj
,A[0][j] = 1
потому что существует ровно один способ представления значения0
: нет монеты.A[8][1]
, число способов представляет собой 8 центов с Пенни и пятаками. Каждое представление будет либо использовать никель, либо нет. если мы не используем никель, то мы можем использовать только Пенни, поэтому естьA[8][0]
способы сделать это. Если мы используем никель, то у нас остается3
центов, поэтому естьA[3][1]
способов сделать это.Для вычисления
A[8][0]
у нас есть только одна монета, так чтоA[8][0] = A[7][0] = ... = A[0][0] = 1
.Для вычисления
A[3][1]
мы не можем использовать никель, так как3 < 5
, так чтоA[3][1] = A[3][0]
. Оттуда мы имеемA[3][0] = A[2][0] = ... = 1
, как и выше.В целом:
Этот алгоритм работает для любого набора значений монет.A[i][j] = A[i][j-1] if i < C[j] else A[i][j-1] + A[i-C[j]][j]
В то время как ваша реализация является поворотом типичной проблемы смены монет с 1 размерностью меньше, что может привести к двойному подсчету многих комбинаций, я думаю, что это не так сложно для данной системы монет.
Я думаю, что решение просто
floor(N/5) + 1
Поскольку вы можете использовать только
[0..N/5]
5 центов, остальные оставшиеся вы должны использовать 1 центЭто может быть сделано, что легко, потому что ваша система монет каноническая и жадный алгоритм / мышление может быть прикладная.
Если вы настаиваете на использовании динамического программирования для решения проблемы, вам нужно добавить одно измерение, которое представляет, какой тип монет используется. Этот метод является общим для любых монетных системОпределить
C(x, m):= The # of ways to make number x using first m type of coins
Итак, теперь рекуррентная формула становится:
C(x, m) = C(x-coin_type[m], m) + C(x, m-1)
, означает, что вы выбираете использовать монету m-го типа или не использовать ееВот главный момент, почему этот рекуррент работает, а ваш не работает, это порядок итерации государство.
Используя эту рекуррентную формулу, мы можем сделать что-то вроде
Обратите внимание, что внешний цикл заставляет упорядочивать итерации типа монет. СкажемFor i = 0 to # of coin_type For j = 0 to n C(j, i) = C(j-coin_type[i], i) + C(j, i-1)
coin_type = {1,3,5}
, цикл сначала будет считать способ использования только {1}, затем следующая итерация цикла будет считать способ использования {1,3}, последняя итерация будет считать способ использования {1,3,5}.Вы никогда не будете считать путь, используя {3,1,5}, или {1,5,3}...и т.д. Порядок типа монеты фиксирован, то есть вы не будете двойной рассчитывать, что