尝试使用DP解决这个经典问题时遇到了实施问题。 问题是给出一组硬币,并返回进行更改的方式。
DP等式如下:
DP [i] + = DP [i - coin [j]]
其中DP [i]表示为i进行更改的方式的数量。
这是一个简单的实现,这是不正确的:
int make_change_wrong(int coin[], int size, int change) {
vector<int> DP(change + 1, 0);
DP[0] = 1;
for (int i = 1; i <= change; ++i) {
for (int j = 0; j < size; ++j) {
if (i - coin[j] >= 0 ) {
DP[i] += DP[i - coin[j]];
}
}
}
return DP[change];
}
给定输入: int coin [] = {1,5} 改变= 6。
make_change_wrong(coin,2,6)返回3,但2是正确的。
使用相同的逻辑,我以不太直观的方式重写它并得到正确的答案:
int make_change(int coin[], int size, int change) {
vector<int> DP(change + 1, 0);
DP[0] = 1;
for (int i = 0; i < size; ++i) {
for (int j = coin[i]; j <= change; ++j) {
DP[j] += DP[j - coin[i]];
}
}
return DP[change];
}
这让我很困惑,因为对我来说,他们是一样的...... 有人能说明两个实现中的问题吗?
答案 0 :(得分:4)
你的第一个算法错了。
DP [5] = 2 {1,1,1,1,1},{5}
DP [6] = DP [5] + DP [1] = 3
你正在计算{5,1}两次。 的 EDITED 强> 因此,执行此操作的标准技巧是您保留允许使用的面额计数DP[i,m] = DP[i-coin[m],m] + DP[i,m-1]
表示使用范围[1..m]内的硬币更改i金额的方式的数量。 显然,你要么使用mth面额,要么不使用。
你正在使用的第二个算法是做同样的技巧,但这是一个非常聪明的方法,拿第i个硬币,看看你可以使用它生成的所有变化。这将避免过度计数,因为你避免做{1,5}和{5,1}之类的事情。
答案 1 :(得分:0)
这个问题出现在面试准备书 Cracking the Coding Interview 中,并且书中给出的解决方案根本没有优化。它使用递归(没有DP)重复计算子问题,因此在O(N ^ 3)中运行,这是特别具有讽刺意味的,因为它是动态编程章节的一部分。
这是一个非常简单的工作解决方案(Java),它使用DP并在O(N)时间内运行。
static int numCombos(int n) {
int[] dyn = new int[n + 1];
Arrays.fill(dyn, 0);
dyn[0] = 1;
for (int i = 1; i <= n; i++) dyn[i] += dyn[i - 1];
for (int i = 5; i <= n; i++) dyn[i] += dyn[i - 5];
for (int i = 10; i <= n; i++) dyn[i] += dyn[i - 10];
for (int i = 25; i <= n; i++) dyn[i] += dyn[i - 25];
return dyn[n];
}
答案 2 :(得分:0)
请尝试输入您的第二种方法:
coin[5] = {1,5,10,20,30};
make_change(coin,5,30);
它返回21.请检查我的测试用例。