获得总计的方式数量

鉴于不同面额和总数的硬币,我们可以通过多少种方式将这些硬币结合起来获得总额?假设我们有 coins = {1, 2, 3}total = 5,我们可以用 5 种方式获得总数 :

  • 1 1 1 1 1
  • 1 1 1 2
  • 1 1 3
  • 1 2 2
  • 2 3

这个问题与背包问题密切相关。唯一的区别是我们有无限量的硬币供应。我们将使用动态编程来解决这个问题。

我们将使用 2D 数组 dp [n] [total + 1] ,其中 n 是我们拥有的不同硬币面额的数量。对于我们的例子,我们需要 dp [3] [6] 。这里 dp [i] [j] 将表示如果我们从硬币[0]硬币[i] 有硬币我们可以获得 j 的方式。例如, dp [1] [2] 将存储如果我们有硬币[0]硬币[1] ,我们可以用多少种方式制作 2 。让我们开始: **** **** **** **** **** ****

对于 dp [0] [0] ,我们问自己,如果我们只有 1 个面额的硬币,即硬币[0] = 1 ,我们可以通过多少种方式获得 0 ?答案是 1 路,这就是如果我们不采取任何硬币的。继续, dp [0] [1] 将代表我们只有硬币[0] ,我们可以通过多少种方式获得 1 ?答案是 1 。以相同的方式, dp [0] [2]dp [0] [3]dp [0] [4]dp [0] [5] 将为 1 。我们的数组看起来像:

     +---+---+---+---+---+---+---+
(den)|   | 0 | 1 | 2 | 3 | 4 | 5 |
     +---+---+---+---+---+---+---+
 (1) | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
     +---+---+---+---+---+---+---+
 (2) | 1 |   |   |   |   |   |   |
     +---+---+---+---+---+---+---+
 (3) | 2 |   |   |   |   |   |   |
     +---+---+---+---+---+---+---+

对于 dp [1] [0] ,我们问自己是否有 2 个面额的硬币,即如果我们有硬币[0] = 1硬币[1] = 2 ,我们可以用多少种方式制作 0 ?答案是 1 ,这是完全没有硬币。对于 dp [1] [1] ,由于硬币[1] = 2 大于我们当前的总数, 2 将无助于获得总数。所以我们将排除 2 并计算我们可以使用硬币获得总数的方式 [0] 。但是这个值已经存储在 dp [0] [1]中 ! 所以我们将从顶部获取价值。我们的第一个公式

if coins[i] > j
    dp[i][j] := dp[i-1][j]
end if

对于 dp [1] [2] ,我们可以通过多少种方式得到 2 ,如果我们有面额 12 的硬币?我们可以使 2 枚使用硬币的面额的 1 ,其由下式表示 DP [0] [2] ,再次我们可以采取 1 种的面额 2 ,其被存储在 DP [1] [2-硬币[I]] ,其中 = 1 。为什么?如果我们看下一个例子就会很明显。对于 dp [1] [3] ,如果我们有 12 的面额硬币,我们可以通过多少种方式获得 3 ?我们可以做 3 **** **** **** 使用币值的硬币 11 种方式,其被存储在 DP [0] [3] 。现在,如果我们取 1 的面额为 2 ,我们需要 3 - 2 = 1 来得到总数。使用面额 12 的硬币获得 1 的方式的数量存储在 dp [1] [1]中,其可以写为 dp [i] [j-coins [i]] ,其中 i = 1 。这就是我们以这种方式编写前一个值的原因。我们的第二个也是最后一个公式是: **** **** **** **** **** ****

if coins[i] <= j
    dp[i][j] := dp[i-1][j] + dp[i][j-coins[i]]
end if

这是填充整个 dp 数组所需的两个公式。填满阵列后会看起来像:

     +---+---+---+---+---+---+---+
(den)|   | 0 | 1 | 2 | 3 | 4 | 5 |
     +---+---+---+---+---+---+---+
 (1) | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
     +---+---+---+---+---+---+---+
 (2) | 1 | 1 | 1 | 2 | 2 | 3 | 3 |
     +---+---+---+---+---+---+---+
 (3) | 2 | 1 | 1 | 2 | 3 | 4 | 5 |
     +---+---+---+---+---+---+---+

dp [2] [5] 将包含我们所需的答案。算法:

Procedure CoinChange(coins, total):
n := coins.length
dp[n][total + 1]
for i from 0 to n
    dp[i][0] := 1
end for
for i from 0 to n
    for j from 1 to (total + 1)
        if coins[i] > j
            dp[i][j] := dp[i-1][j]
        else
            dp[i][j] := dp[i-1][j] + dp[i][j-coins[i]]
        end if
    end for
end for
Return dp[n-1][total]

该算法的时间复杂度为 O(n * total),其中 n 是我们拥有的硬币的面额数。