分硬币问题

分硬币问题是一个很有意思的问题,一般可以用recursive或者dp来解决。Reference里面acm之家的文章分析得很好,下面我自己再重复一边这个过程。这道题也可以联想到其他dp过程。

题目很简单: 给定一堆硬币coins[], 数量管够,硬币有m种不同的面值,求组成 n 元钱有多少种不同的方法。

  1. 首先我们考虑递归。
    1. 当n < 0的时候,我们返回0
    2. 当n = 0的时候,我们返回1,只有一种组合就是所有硬币都取0个
    3. 当m <= 0的时候,没有可以用的硬币种类了,我们返回0
    4. 其他情况,我们可以分为两种
      1. 不使用第m种硬币,那么我们可以递归求解子问题,结果应该是count(coins, m - 1, n)
      2. 至少使用一次第m种硬币,也是分解为相同子问题,结果是count(coins, m, n - coins[m - 1])
  2. 接下来我们考虑用dynamic programming来解决,也就是把上面的逻辑转换为记忆化搜索
    1. 首先我们要建立一个数组dp[][] = new int[n + 1][m]
    2. 初始化这个数组,把第一行设置为1。因为这里n = 0,所以我们每个硬币都只有一种方法,就是不选择这些硬币,所以第一行都为1
    3. 当i 从 1 到  dp.length, j从0到m进行遍历的时候,我们依然是分为两种情况进行考虑
      1. 使用第j种硬币:
        1. 那么假如当前  i - coins[j] >= 0的话,我们可以使用第j种硬币,相应的值为dp[i - coins[j]][j]。意思是我们减掉这个coins[j]的值,到之前已经保存的i - coins[j]这一行的第j列去看有多少种方法
        2. 否则我们不能使用这类硬币,值为0
      2. 不适用第j种硬币:
        1. 那么假如j > 1的话,我们可以有多少种方法完全决定于同一行内的上一个数据dp[i][j - 1]
        2. 否则值为0
      3. 把这两种情况综合一下 x + y, 就是dp[i][j]应该有的值了。
  3. 接下来我们对上面的dp进行空间上的简化,用滚动数组来代替二维数组:
    1. 声明一个滚动数组table = new int[n + 1]
    2. table[0] = 1
    3. 当i 从 0 到 m, j 从dp[i] 到 n + 1遍历的时候 table[j] += table[j - coins[i]]
    4. 举个例子,假如 n = 10,我们有1, 2 ,5这三种硬币,那么过程如下
      1. 初始化                [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
      2. 使用1                     [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
      3. 使用1和2                [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6]
      4. 使用1,2和5             [1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 10]

C#:

2D DP

            int[] coins = { 1, 2, 5 };
            int m = coins.Length;
            int n = 100;
            int[,] dp = new int[n + 1, m];
            for (int i = 0; i < m; i++)
            {
                dp[0, i] = 1;
            }

            for (int i = 1; i < n + 1; i++)         // 2d dp
            {
                for (int j = 0; j < m; j++)
                {
                    int x = (i - coins[j] >= 0) ? dp[i - coins[j], j] : 0;
                    int y = (j > 0) ? dp[i, j - 1] : 0;
                    dp[i, j] = x + y;
                }
            }
            Console.WriteLine(dp[n, m - 1]);

滚动数组DP

            int[] coins = { 1, 2, 5 };
            int m = coins.Length;
            int n = 100;
         
            int[] table = new int[n + 1];
            table[0] = 1;
            for (int i = 0; i < m; i++)
            {
                for (int j = coins[i]; j < table.Length; j++)
                {
                    table[j] += table[j - coins[i]];
                }
            }
            Console.WriteLine(table[n]);

 

Update:  6-20-2016

To be updated:  

1. n元钱有多少种组成方式

2. 最少可以用多少个硬币来组成n元钱

 

Reference:

http://www.acmerblog.com/dp6-coin-change-4973.html

http://www.geeksforgeeks.org/dynamic-programming-set-7-coin-change/

posted @ 2015-11-06 05:17  YRB  阅读(821)  评论(0编辑  收藏  举报