我正在尝试理解硬币更换问题解决方案,但遇到了一些困难。
在the Algorithmist,有一个动态编程解决方案的伪代码解决方案,如下所示:
n = goal number
S = [S1, S2, S3 ... Sm]
function sequence(n, m)
//initialize base cases
for i = 0 to n
for j = 0 to m
table[i][j] = table[i-S[j]][j] + table[i][j-1]
这是一个非常标准的O(n^2)
算法,它可以避免使用二维数组多次重新计算相同的答案。
我的问题有两个方面:
table[][]
作为初始值关于问题1,此算法有三种基本情况:
if n==0, return 1
if n < 0, return 0
if n >= 1 && m <= 0, return 0
如何将它们合并到table[][]
,我不确定。最后,我不知道如何从数组中提取解决方案集。
答案 0 :(得分:2)
我们可以在至少两种不同的方法中实现动态编程算法。一种是使用memoization的自上而下方法,另一种是自下而上的迭代方法。
对于初学者到动态编程,我总是建议首先使用自上而下的方法,因为这将有助于他们理解动态编程中的递归关系。
因此,为了解决硬币变化问题,您已经了解了重复关系的含义:
table[i][j] = table[i-S[j]][j] + table[i][j-1]
这种递归关系是好的,但由于它没有任何边界条件,所以没有明确定义。因此,我们需要定义边界条件,以确保重复关系可以成功终止而不会进入无限循环。
那么当我们试图沿着递归树走下去会发生什么呢?
如果我们需要计算table[i][j]
,这意味着使用i
类型到0
的硬币更改j
的方法数量,我们需要一些基本案例处理:
1)如果j == 0
怎么办?
如果j == 0
我们将尝试解决子问题table(i,j-1)
,这不是一个有效的子问题。因此,一个边界条件是:
if(j==0) {
if(i==0) table[i][j] = 1;
else table[i][j] = 0;
}
2)如果i - S[j] < 0
怎么办?
我们还需要处理这种边界情况,我们知道在这种情况下我们应该不尝试解决这个子问题或者为所有这些情况初始化table(i-S[j],j) = 0
。
总而言之,如果我们要从自上而下的memoization方法实现这个动态编程,我们可以这样做:
int f(int i, int j) {
if(calc[i][j]) return table[i][j];
calc[i][j] = true;
if(j==0) {
if(i==0) return table[i][j]=1;
else return table[i][j]=0;
}
if(i>=S[j])
return table[i][j]=table[i-S[j][j]+table[i][j-1];
else
return table[i][j]=table[i][j-1];
}
在实践中,我们也可以使用table
数组的值来帮助跟踪之前是否已经计算过这个子问题(例如,我们可以初始化值为-1表示此子问题没有'经过计算)。
希望答案清楚。 :)