找零

问题:

假设有m种面值不同的硬币,个个面值存于数组S ={S1,S2,… Sm}中,现在用这些硬币来找钱,各种硬币的使用个数不限。 求对于给定的钱数N,我们最多有几种不同的找钱方式。硬币的顺序并不重要。

例如,对于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。

1)最优子结构
要算总数的解决方案,我们可以把所有的一整套解决方案在两组 (其实这个方法在组合数学中经常用到,要么包含某个元素要么不包含,用于递推公式等等,)。
1)解决方案不包含 第m种硬币(或Sm)。
2)解决方案包含至少一个 第m种硬币。
让数(S [] , M, N)是该函数来计算解的数目,则它可以表示为计数的总和(S [], M-1, N)和计数(S [],M,N-Sm)。

因此,这个问题具有最优子结构性质的问题。

2) 重叠子问题

下面是一个简单的递归实现硬币找零问题。遵循上面提到的递归结构。

01 #include<stdio.h>
02 int count( int S[], int m, int n )
03 {
04     // 如果n为0,就找到了一个方案
05     if (n == 0)
06         return 1;
07     if (n < 0)
08         return 0;
09     // 没有硬币可用了,也返回0
10     if (m <=0 )
11         return 0;
12     // 按照上面的递归函数
13     return count( S, m - 1, n ) + count( S, m, n-S[m-1] );
14 }
15  
16 // 测试
17 int main()
18 {
19     int i, j;
20     int arr[] = {1, 2, 3};
21     int m = sizeof(arr)/sizeof(arr[0]);
22     printf("%d ", count(arr, m, 4));
23     getchar();
24     return 0;
25 }

应当指出的是,上述函数反复计算相同的子问题。见下面的递归树为S = {1,2,3},且n = 5。
的函数C({1},3)被调用两次。如果我们绘制完整的树,那么我们可以看到,有许多子问题被多次调用。

01 C() --> count()
02                               C({1,2,3}, 5)                    
03                            /                \
04                          /                   \             
05              C({1,2,3}, 2)                 C({1,2}, 5)
06             /     \                        /         \
07            /        \                     /           \
08 C({1,2,3}, -1)  C({1,2}, 2)        C({1,2}, 3)    C({1}, 5)
09                /     \            /    \            /     \
10              /        \          /      \          /       \
11     C({1,2},0)  C({1},2)   C({1,2},1) C({1},3)    C({1}, 4)  C({}, 5)
12                    / \      / \       / \        /     \   
13                   /   \    /   \     /   \      /       \
14                 .      .  .     .   .     .   C({1}, 3) C({}, 4)
15                                                /  \
16                                               /    \ 
17                                              .      .

所以,硬币找零问题具有符合动态规划的两个重要属性。像其他典型的动态规划(DP)的问题,可通过自下而上的方式打表,存储相同的子问题。当然上面的递归程序也可以改写成记忆化存储的方式来提高效率。

下面是动态规划的程序:

01 #include<stdio.h>
02  
03 int count( int S[], int m, int n )
04 {
05     int i, j, x, y;
06  
07     // 通过自下而上的方式打表我们需要n+1行
08     // 最基本的情况是n=0
09     int table[n+1][m];
10  
11     // 初始化n=0的情况 (参考上面的递归程序)
12     for (i=0; i<m; i++)
13         table[0][i] = 1;
14  
15     for (i = 1; i < n+1; i++)
16     {
17         for (j = 0; j < m; j++)
18         {
19             // 包括 S[j] 的方案数
20             x = (i-S[j] >= 0)? table[i - S[j]][j]: 0;
21  
22             // 不包括 S[j] 的方案数
23             y = (j >= 1)? table[i][j-1]: 0;
24  
25             table[i][j] = x + y;
26         }
27     }
28     return table[n][m-1];
29 }
30  
31 // 测试
32 int main()
33 {
34     int arr[] = {1, 2, 3};
35     int m = sizeof(arr)/sizeof(arr[0]);
36     int n = 4;
37     printf(" %d ", count(arr, m, n));
38     return 0;
39 }

时间复杂度:O(mn)

以下为上面程序的优化版本。这里所需要的辅助空间为O(n)。因为我们在打表时,本行只和上一行有关,类似01背包问题。

01 int count( int S[], int m, int n )
02 {
03     int table[n+1];
04     memset(table, 0, sizeof(table));
05     //初始化基本情况
06     table[0] = 1;
07  
08     for(int i=0; i<m; i++)
09         for(int j=S[i]; j<=n; j++)
10             table[j] += table[j-S[i]];
11  
12     return table[n];
13 }

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

http://www.algorithmist.com/index.php/Coin_Change

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值