【问题标题】:space optimized solution for coin change硬币找零的空间优化解决方案
【发布时间】:2015-03-08 23:18:22
【问题描述】:

给定一个 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。但无法理解仅使用一维数组 table[] 的空间优化动态编程方法。

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

【问题讨论】:

    标签: java algorithm dynamic-programming


    【解决方案1】:

    试着用这种方式理解算法。

    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个硬币组成j-S[i]值。因此,总和为两者之和,即table[i-1][j] + table[i][j-S[i]]

    在代码中,您将看到 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] right now 表示使用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

    【讨论】:

    • 感谢qqibrow的回复。但我的问题是关于仅使用一维数组的解决方案的空间优化版本(请参阅我复制粘贴的代码)。我无法理解。
    • @Walt 其实我是在解释空间优化。我的值在你的循环中吗?我添加了更多细节,希望对您有所帮助。
    • 为什么要向后循环? @qqibrow
    • @Permian 向后循环确保每个硬币只能使用一次。等式是 table[i][j] = table[i-1][j] + table[i][j-S[i]] 。如果继续前进,table[i][j-S[i]] 可能只是在同一循环中更新的值;如果倒退,那不可能发生。
    【解决方案2】:

    首先注意table[i]是N=i时换币的方式数。

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

    每个硬币按以下方式将值加起来到 table[] 中。

    对于价值 X 的硬币,以下是 table[] 的更新 -

    1. 表[X] = 表[X] + 1

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

    2. 对于所有 Y > X

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

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

      4 = 3 + 1 即 4 可以通过组合 3 和 1 获得。根据定义,获得 1 的方法数是 table[1]。所以很多方法被添加到表[4]中。这就是为什么上面的表达式使用 table[Y-X]。

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

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

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

    【讨论】:

    • 所以这需要一个排序的 S[] 对吗?否则它对于像 {3,2,6,5,4} 这样的 S 是如何工作的
    • 没有。不需要排序的 S[]。在算法结束时,table[] 的 {3,2,6,5,4} 和 {2,3,4,5,6} 应该具有相同的值。
    • 你能帮我试一试 {6,5,2,4} .. 我很困惑 :(
    • @Walt。嗨,沃尔特!我为我的答案添加了更多细节和试运行。
    【解决方案3】:

    会尝试为其他人解释..

    考虑这段代码 -

    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],或者完全删除行索引,这将导致所有值的所有数据都累积在dp[j] i.

    第二种方法的代码 -

    dp = [0 for i in range(n+1)]
    
    dp[0] = 1
    for i in range(len(coins)):
        for j in range(coins[i], n+1):
            dp[j] += dp[j-coins[i]]
    
    return dp[n]
    

    【讨论】:

      猜你喜欢
      • 2015-09-02
      • 2013-12-09
      • 1970-01-01
      • 2022-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-01
      • 2018-10-07
      相关资源
      最近更新 更多