Понимание алгоритма изменения
Я искал хорошее решение проблемы Изменение проблемы, и я нашел этот код (Python):
target = 200
coins = [1,2,5,10,20,50,100,200]
ways = [1]+[0]*target
for coin in coins:
for i in range(coin,target+1):
ways[i]+=ways[i-coin]
print(ways[target])
У меня нет проблем с пониманием того, что код буквально делает, но я не могу понять, ПОЧЕМУ он работает.
Кто может помочь?
Ответы
Ответ 1
Чтобы получить все возможные множества, что элементы равны "a" или "b" или "c" (наши монеты), которые суммируются до "X", вы можете:
- Возьмем все такие множества, которые суммируются с X-a и добавим к ним дополнительный "a".
- Возьмем все такие наборы, которые суммируются с X-b и добавим к ним дополнительный "b".
- Возьмем все такие множества, которые суммируются с точностью до X-c и добавят к ним каждый дополнительный c.
Таким образом, количество способов, которыми вы можете получить X, - это сумма чисел, с помощью которых вы можете получить X-a и X-b и X-c.
ways[i]+=ways[i-coin]
Отдых - это простое повторение.
Дополнительный намек:
в начале вы можете получить набор с нулевой суммой одним способом (пустой набор)
ways = [1]+[0]*target
Ответ 2
Это классический пример динамического программирования. Он использует кэширование, чтобы избежать ошибок при подсчете таких вещей, как
3 + 2 = 5 дважды (из-за другого возможного решения: 2 + 3). Рекурсивный алгоритм попадает в эту ловушку. Давайте сосредоточимся на простом примере, где target = 5
и coins = [1,2,3]
. Размещенный вами код имеет значение:
- 3 + 2
- 3 + 1 + 1
- 2 + 2 + 1
- 1 + 2 + 1 + 1
- 1 + 1 + 1 + 1 + 1
в то время как рекурсивная версия имеет значение:
- 3 + 2
- 2 + 3
- 3 + 1 + 1
- 1 + 3 + 1
- 1 + 1 + 3
- 2 + 1 + 2
- 1 + 1 + 2
- 2 + 2 + 1
- 2 + 1 + 1 + 1
- 1 + 2 + 1 + 1
- 1 + 1 + 2 + 1
- 1 + 1 + 1 + 2
- 1 + 1 + 1 + 1 + 1
Рекурсивный код:
coinsOptions = [1, 2, 3]
def numberOfWays(target):
if (target < 0):
return 0
elif(target == 0):
return 1
else:
return sum([numberOfWays(target - coin) for coin in coinsOptions])
print numberOfWays(5)
Динамическое программирование:
target = 5
coins = [1,2,3]
ways = [1]+[0]*target
for coin in coins:
for i in range(coin, target+1):
ways[i]+=ways[i-coin]
print ways[target]
Ответ 3
Основная идея кода заключается в следующем:
"На каждом шаге есть ways
способы внести изменения в сумму i
при выплате монет [1,...coin]
".
Итак, на первой итерации у вас есть только монета с наименованием 1
. Я считаю, что очевидно, что есть только один способ дать изменение, имеющее только эти монеты для любой цели. На этом шаге ways
массив будет выглядеть как [1,...1]
(только один путь для всех целей от 0
до target
).
На следующем шаге добавим монету с наименованием 2
к предыдущему набору монет. Теперь мы можем подсчитать, сколько комбинаций изменений для каждой цели от 0
до target
.
Очевидно, что количество комбинаций будет увеличиваться только для целей >= 2
(или добавлена новая монета в общем случае). Таким образом, для цели равна 2
количество комбинаций будет ways[2](old)
+ ways[0](new)
. Как правило, каждый раз, когда i
равна новой введенной монете, нам нужно добавить 1
к предыдущему числу комбинаций, где новая комбинация будет состоять только из одной монеты.
Для target
> 2
ответ будет состоять из суммы "всех комбинаций суммы target
, имеющей все монеты меньше coin
", и "все комбинации суммы coin
, имеющие все монеты меньше coin
сам".
Здесь я описал два основных шага, но я надеюсь, что это легко обобщить.
Позвольте мне показать вам вычислительное дерево для target = 4
и coins=[1,2]
:
пути [4] для монет = [1,2] =
пути [4], указанные монеты = [1] + пути [2], данные монеты = [1,2] =
1 + пути [2] заданы монеты = [1] + пути [0] с учетом монет = [1,2] =
1 + 1 + 1 = 3
Итак, есть три способа дать изменение: [1,1,1,1], [1,1,2], [2,2]
.
Приведенный выше код полностью эквивалентен рекурсивному решению. Если вы понимаете рекурсивное решение, я уверен, вы понимаете решение, приведенное выше.
Ответ 4
Решение, которое вы опубликовали, представляет собой обобщенную версию этого кода.
/// <summary>
/// We are going to fill the biggest coins one by one.
/// </summary>
/// <param name="n"> the amount of money </param>
public static void MakeChange (int n)
{
int n1, n2, n3; // residual of amount after each coin
int quarter, dime, nickel; // These are number of 25c, 10c, 5c, 1c
for (quarter = n/25; quarter >= 0; quarter--)
{
n1 = n - 25 * quarter;
for (dime = n1/10; dime >= 0; dime--)
{
n2 = n1 - 10 * dime;
for (nickel = n2/5; nickel >= 0 && (n2 - 5*nickel) >= 0; nickel--)
{
n3 = n2 - 5 * nickel;
Console.WriteLine("{0},{1},{2},{3}", quarter, dime, nickel, n3); // n3 becomes the number of cent.
}
}
}
}