空间优化的硬币变化解决方案

时间:2015-01-10 20:23:11

标签: java algorithm dynamic-programming

给定值N,如果我们想要改变N美分,并且我们每个S = {S1,S2,..,Sm}值的硬币都有无限供应,我们可以通过多少方式进行更改?硬币的顺序无关紧要。

例如,对于N = 4和S = {1,2,3},有四个解:{1,1,1,1},{1,1,2},{2,2}, {1,3}。因此输出应为4.对于N = 10且S = {2,5,3,6},有五种解决方案:{2,2,2,2,2},{2,2,3,3}, {2,2,6},{2,3,5}和{5,5}。所以输出应该是5。

我找到了3种方法HERE。但是无法理解空间优化的动态编程方法,其中仅使用单维数组表[]。

int count( int S[], int m, int n )
{
    // table[i] will be storing the number of solutions for
    // value i. We need n+1 rows as the table is consturcted
    // in bottom up manner using the base case (n = 0)
    int table[n+1];

    // Initialize all table values as 0
    memset(table, 0, sizeof(table));

    // Base case (If given value is 0)
    table[0] = 1;

    // Pick all coins one by one and update the table[] values
    // after the index greater than or equal to the value of the
    // picked coin
    for(int i=0; i<m; i++)
        for(int j=S[i]; j<=n; j++)
            table[j] += table[j-S[i]];

    return table[n];
}

3 个答案:

答案 0 :(得分:4)

尝试使用这种方式理解算法。

table[i][j]表示使用第一个i种类型的硬币来更改值j。然后:

table[i][j] = table[i-1][j] + table[i][j-S[i]]

显然,在制作j硬币时,您有两种选择。不使用第i个硬币或使用第i个硬币。不使用第i个硬币时,解决方案编号为table[i-1][j]。当使用第i个硬币时,解数为table[i][j-S[i]],这意味着使用前i个硬币来组成jS [i]值。因此,总数是两者的总和,即table[i-1][j] + table[i][j-S[i]] < / p>

在代码中,您将看到for循环。外部循环遍历i,内部循环遍历j。 +=语句根据上面的等式计算table[i][j]

修改

您的代码中的

table[j]实际上是我在上面讨论的table[i][j]i是循环中的值。在循环table[N]表示table[M][N]之后,使用第一个M硬币代表所有硬币,为N创造价值。

我将根据代码提供更多详细信息:

 for(int i=0; i<m; i++)
        for(int j=S[i]; j<=n; j++)
            table[j] += table[j-S[i]];

i = 0时,table[j]表示使用前1个硬币对值j进行更改。例如,table[2]现在意味着使用coins {1}来更改2.所以:

table[1] = table[1] + table[1 - S[0]] = table[1] + table[0] = 1 + 0= 1
table[2] = table[2] + table[2 - S[0]] = table[2] + table[1] = 0 + 1= 1
table[3] = 1
table[4] = 1

之后,当i = 0时,我们得到了结果。table[1] ~ table[4]现在意味着使用coin {1}分别对值1,2,3,4进行更改。

当i = 1时,table[j]表示使用coin {1, 2}对值j进行更改。

table[2] = table[2] + table[2 - S[1]] = table[2] + table[0] = 1 + 1= 2
table[3] = table[3] + table[3 - S[1]] = 1 + 1 = 2
table[4] = table[4] + table[4 - S[1]] = table[4] + table[2] = 1 + 2 = 3

以下过程是相同的。

最后,我们在table[4]出来时i = 1并分析它:

table[4] = table[4] + table[4 - S[1]] = table[4] + table[2] = 1 + 2 = 3

左边的table[4]是我们正在计算的值,实际上是table[i=1][4]。右侧的table[4]代表前一个值,在本例中为table[i=0][4]。它可以扩展到:

table[i=1][4] = table[i=0][4] + table[i=1][4 - S[1]]

方程式正好

table[i][j] = table[i-1][j] + table[i][j-S[i]]

编辑跟进问题

如果您认为自己真的理解这个问题,请尝试使用新约束来解决同样的问题。如果每枚硬币只能使用一次怎么办?例如,N = 4且S = {1,2,3},只有一个解{1,3},因此输出应为1.并且对于N = 10且S = {2,5,3,6},仍然只有一个解决方案{2,3,5},输出为1。

提示:原始代码只需更改一行即可。

答案:http://ideone.com/t1JnEz

答案 1 :(得分:2)

首先请注意,表[i]是N = i时硬币变换的方式数。

给定算法根据给定的硬币组(S [])填充此数组(table [])。 最初,table []中的所有值都初始化为0.而table [0]设置为0(这是基本情况N = 0)。

每枚硬币以下列方式在表格[]中累加值。

对于值为X的硬币,以下是对表[] -

的更新
  1. table [X] = table [X] + 1

    这很容易理解。具体来说,这会添加解决方案{X}。

  2. 所有Y的
  3. &gt; X

    表[Y] =表格[Y] +表格[Y-X]

    这很难理解。例子X = 3,考虑Y = 4的情况。

    4 = 3 + 1,即4可以通过组合3和1获得。并且根据定义,获得1的方式的数量是表[1]。因此,表[4]中添加了许多方法。这就是为什么上面的表达式使用表[Y-X]。

  4. 算法中的以下行代表相同的(以上两个步骤) -

    table[j] += table[j-S[i]];  
    

    在算法结束时,table [n]包含n的解决方案。

答案 2 :(得分:0)

将尝试为他人进行解释。

考虑这段代码-

dp = [[0 for i in range(len(coins))] for j in range(n+1)]
for i in range(n+1):
    for j in range(len(coins)):
        if i == 0:
            dp[i][j] = 1
        elif j == 0:
            dp[i][j] = 0
        else:
            dp[i][j] = dp[i][j-1]

        if i - coins[j] >= 0:
            dp[i][j] += dp[i-coins[j]][j]

print dp[n][len(coins)-1]

这种方法是非常基本的,没有空间优化。最初,我们可能认为我们只是从列索引j - 1访问信息,所以我们可以删除列,但事实并非如此,因为我们也在访问dp[i - coins[j]][j]。因此,j'th索引中包含的信息很有用,我们不能删除这些列。

现在考虑一下,

dp = [[0 for i in range(n+1)] for j in range(len(coins))]
for i in range(len(coins)):
    for j in range(n+1):
        if j == 0:
            dp[i][j] = 1
        elif i == 0:
            dp[i][j] = 0
        else:
            dp[i][j] = dp[i-1][j]

        if j - coins[i] >= 0:
            dp[i][j] += dp[i][j-coins[i]]

print dp[len(coins)-1][n]

我们所做的所有事情都与for循环的顺序相反。通过仔细观察,我们可以看到dp[i][j-coins[i]]dp[i][j]来自同一行。那么这是否意味着我们不需要维护其他行的信息?是的,我们没有。

现在有两种解决方法,要么维护两个数组,一个用于dp[i],另一个用于dp[i-1],或者完全删除行索引,这将导致所有数据在{{ 1}}用于dp[j]的所有值。

第二种方法的代码-

i