Algorithm 更改硬币的执行问题

Algorithm 更改硬币的执行问题,algorithm,dynamic-programming,Algorithm,Dynamic Programming,在尝试使用DP解决这个经典问题时,我遇到了一个实现问题。 问题是给定一组硬币,并返回进行更改的方法数 DP方程如下所示: DP[i]+=DP[i-硬币[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

在尝试使用DP解决这个经典问题时,我遇到了一个实现问题。 问题是给定一组硬币,并返回进行更改的方法数

DP方程如下所示: DP[i]+=DP[i-硬币[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];
}
给定输入: 整数硬币[]={1,5} 变化=6

make_change_error硬币,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];
}
这让我很困惑,因为对我来说,他们是一样的。。。
有人能解释一下这两个实现中的问题吗

您的第一个算法是错误的

DP[5]=2{1,1,1,1},{5}

DP[6]=DP[5]+DP[1]=3

你在数{5,1}两次。 编辑 所以做这件事的标准窍门是,你要记下允许你使用的面额

DP[i,m] = DP[i-coin[m],m] + DP[i,m-1]
这意味着使用[1..m]范围内的硬币改变i数量的方法的数量。 很明显,你要么使用mth面额,要么不使用


你正在使用的第二种算法是做同样的把戏,但这是一个非常聪明的方法,拿第i枚硬币,看看你能用它产生什么样的变化。这将避免过度计数,因为您避免执行类似{1,5}和{5,1}的操作

您的第一个算法是错误的

DP[5]=2{1,1,1,1},{5}

DP[6]=DP[5]+DP[1]=3

你在数{5,1}两次。 编辑 所以做这件事的标准窍门是,你要记下允许你使用的面额

DP[i,m] = DP[i-coin[m],m] + DP[i,m-1]
这意味着使用[1..m]范围内的硬币改变i数量的方法的数量。 很明显,你要么使用mth面额,要么不使用


你正在使用的第二种算法是做同样的把戏,但这是一个非常聪明的方法,拿第i枚硬币,看看你能用它产生什么样的变化。这将避免过度计数,因为您避免执行类似{1,5}和{5,1}的操作

这个问题出现在面试准备书中,破解了编码面试,书中给出的解决方案根本没有优化。它使用无DP的递归反复计算子问题,因此在^3上运行,这尤其具有讽刺意味,因为它是动态规划章节的一部分

下面是一个非常简单的Java工作解决方案,它使用DP并按时运行

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];
}

这个问题存在于面试准备书中,该书中给出的解决方案根本没有优化。它使用无DP的递归反复计算子问题,因此在^3上运行,这尤其具有讽刺意味,因为它是动态规划章节的一部分

下面是一个非常简单的Java工作解决方案,它使用DP并按时运行

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];
}

请尝试输入第二种方法:

coin[5] = {1,5,10,20,30};
make_change(coin,5,30);

它返回21。请检查我的测试用例。

请尝试输入第二种方法:

coin[5] = {1,5,10,20,30};
make_change(coin,5,30);

它返回21。请检查我的测试用例。

如果您在两个实现之间得到了不同的答案,那么很明显它们是不等价的。也许您可以插入一些调试语句,以便在构建DP数组时打印出该数组的内容;这可能有助于说明它们是如何不同的。感谢Brian,我知道第一个函数计数{1,1,1,1}两次,但我不明白为什么第二个函数计数正确-它们似乎只以两种不同的方式编码。如果你在两个实现之间得到不同的答案,那么显然它们是不等价的。也许您可以插入一些调试语句,以便在构建DP数组时打印出该数组的内容;这可能有助于说明它们是如何不同的。感谢Brian,我知道第一个函数计数{1,1,1,1}两次,但我不明白为什么第二个函数计数正确-它们似乎只以两种不同的方式编码。感谢Sukunt,我知道我的第一个函数得到这个输入错误,它计数{1,1,1,1}两次。我只是不明白为什么使用相同的思想编码会产生不同的行为。感谢Sukunt,我知道我的第一个函数错误地得到了这个输入,并且它计数{1,1,1,1}两次。我只是不明白为什么使用相同的想法编码会产生不同的行为。在CCI的书《111》中观察得很好!对于许多问题,我认为提供的解决方案不是很整洁。你的代码和我的第二次尝试一样重要。关于DP的一点是,即使我们已经成功地获得了递归方程,正确地实现它仍然很困难!对于许多问题,我认为提供的解决方案不是很整洁。你的代码和我的第二次尝试一样重要。关于DP的一点是,即使我们成功地获得了递归方程,正确实现它仍然很困难。