题目描述
有面值分别为1,2,5,10,20,50,100,200的硬币,现要凑齐200的面值,请问一共有多少种凑法。
问题分析
不正确的解法
直接想到的方法是使用动规:
状态转换方程:f(x) = sum { f(x - a[i]) }
注:a[i]为各种面值
# Coin sums
COINS = [1, 2, 5, 10, 20, 50, 100, 200]
memorandum = {}
def calculate(currency):
if currency < 0:
return 0
elif currency == 0:
return 1
# if memorandum has, return value
elif currency in memorandum:
return memorandum[currency]
else:
global COINS
result = 0
for coin in COINS:
result += calculate(currency - coin)
# set this value to memorandum
memorandum[currency] = result
if __name__ == '__main__':
for i in xrange(200 + 1):
calculate(i)
for ele in memorandum:
print(str(ele) + ' : ' + str(memorandum[ele]))
但我们仔细想一下就会发现一点问题。如果当前的面值为3,那么1+2与2+1都会被看作是可行的解,但实际上这两个是同一种找零。
这样的答案肯定是不对的。
问题的原因
仔细想一下就会发现,出现这个问题的原因在于我们同时使用了所有的硬币参与动规而没有对此加以约束,那么当前面值下的不同路径子集树可能会出现同一种组合。
如何规避这些重复的结果 ?
一种可行的方法是先假设只有一种硬币,得出一个解,在此之上依次增加硬币的种类,逐一进行替换。
C#代码:
static void Main(string[] args)
{
int target = 200;
int[] coinSizes = { 1, 2, 5, 10, 20, 50, 100, 200 };
int[] ways = new int[target + 1];
ways[0] = 1;
for (int i = 0; i < coinSizes.Length; i++)
{
for (int j = coinSizes[i]; j <= target; j++)
{
ways[j] += ways[j - coinSizes[i]];
}
}
Console.WriteLine(ways[200]);
}