春招好题 | 动态规划——硬币问题及其变形

Posted 小K技术碎笔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了春招好题 | 动态规划——硬币问题及其变形相关的知识,希望对你有一定的参考价值。

小K最近在笔试中遇到一道挺有(bu)意(hui)思(zuo)的编程题,给大家分享一下。原题如下:


题目描述

小Q去商场购物,经常会遇到找零的问题。

小Q现在手上有n种不同面值的硬币,每种面值的硬币都有无限多个。

为了方便购物,小Q希望带尽量少的硬币,并且要能组合出1到m之间(包含1和m)的所有面值。

输入描述

第一行包含两个整数m, n(1<=n<=100,1<=m<=10^9),含义如题目所述。

接下来的n行,每行一个整数,第i+1行的整数表示第i种硬币的面值

输出描述

输出一个整数,表示最少需要携带的硬币数量。如果无解,则输出-1。

示例(输入输出示例仅供调试,后台判题数据一般不包含示例)

输入

20 4

1

2

5

10


输出

5


乍一看就是一道简单的DP,小K还高兴了一会。但是仔细理解题意会发现,这道题并没有看上去那么简单。且听我慢慢分析...


难度等级☆:给定N种硬币和钱数M,求使用的最少钱币数

这就是一个简单的一维DP,构建一个长度为M+1的数组dp和长度为N的数组coins(记录每种钱币的面值)。若存在某个面值的钱币,则对于该面值的钱数只需要一个钱币即可,所以有:

dp[coins[i]] = 1(i=1,2,3,...,N-1)

对于其它情况,只须考虑最后一个钱币的面值,将问题规模降下来,利用前面动态规划的结果,有:

dp[j] = min(dp[j-coins[i]] + 1) 

其中 j = 1,2,3,...,M 且 j ≠ coins[i], i = 1,2,3,...,N-1

代码如下:

#include <iostream>#include <vector>#include <limits.h>using namespace std;int main() { int m,n; cin>>m>>n;    vector<int> coins(n, 0);  //每种钱币的面值 vector<int> dp(m+1,0); for(int i = 0; i < n; i++){ cin>>coins[i]; dp[coins[i]] = 1; } for(int i = 1; i <= m; i++){ int tmp = INT_MAX; for(int j = 0; j < n; j++){ if(i - coins[j] >= 0){ tmp = min(tmp, dp[i-coins[j]] + 1); } } dp[i] = tmp; } cout<<dp[m]<<endl; return 0;}


难度等级☆☆:给定N种硬币和钱数M,计算有多少种获得M的钱币组合

    问题变成了二维DP,我们可以构建一个(N+1)×(M+1)的二维数组dp,dp[i][sum]表示用前i种硬币构成sum的所有组合数。

    那么题目的问题实际上就是求dp[N][M],即用前N种硬币(所有硬币)构成M的所有组合数。

    我们也可以这么考虑,我们希望用N种硬币构成M,根据最后一个硬币Vm的系数的取值为无非有这么几种情况,xm分别取{0, 1, 2, …, sum/Vm}。可以表达为下面的等式:

sum = x1 * V1 + x2 * V2 + … + 0 * Vm

sum = x1 * V1 + x2 * V2 + … + 1 * Vm

sum = x1 * V1 + x2 * V2 + … + 2 * Vm

sum = x1 * V1 + x2 * V2 + … + K * Vm

其中K是该xm能取的最大数值K = sum / Vm

在上面的联合等式中:当xn=0时,有多少种组合呢? 实际上就是前i-1种硬币组合sum,有dp[i-1][sum]种! xn = 1 时呢,有多少种组合? 实际上是用前i-1种硬币组合成(sum - Vm)的组合数,有dp[i-1][sum -Vm]种; xn =2呢, dp[i-1][sum - 2 * Vm]种,等等。所有的这些情况加起来就是我们的dp[i][sum]。所以:

dp[i][sum] = dp[i-1][sum - 0*Vm] + dp[i-1][sum - 1*Vm] + dp[i-1][sum - 2*Vm] + … + dp[i-1][sum - K*Vm]; 其中K = sum / Vm

用数学公式表达就是:

可以发现,dp数组第i行的值总是依赖第i-1行的值,我们可以逐行求解该数组。初始情况下dp[0][i] = 0(不用任何钱币是换不了钱的)

上代码:

#include <iostream>#include <vector>using namespace std;int main() { int m,n; cin>>m>>n; vector<int> coins(n,0); for(int i = 0; i < n; i++) cin>>coins[i]; vector<vector<int>> dp(n + 1, vector<int>(m+1,0)); for(int i = 0; i <= n; i ++) dp[i][0] = 1; for(int i = 1; i <= n; i++){ for(int j = 1; j <= m; j++){ for(int k = 0; k <= j/coins[i-1]; k++){ dp[i][j] += dp[i-1][j - k * coins[i-1]]; } } } cout<<dp[n][m]<<endl; return 0;}


简单测试一下:

春招好题 | 动态规划——硬币问题及其变形(一)

用4种硬币:1,2,5,10组成面值为5的钱币有4种方法:

1,1,1,1,1

1,1,1,2

1,2,2

5

春招好题 | 动态规划——硬币问题及其变形(一)

用2,3,5三种硬币(当然没人会这样设计硬币面值)组成面值为10的钱币有4种方法:

2,2,2,2,2

2,2,3,3

2,3,5

5,5

用我们平时常用的1,5,10,20,50,100面值的钱币组成100块钱居然有344种不同的组合,想不到吧!?

今天先到这儿,之后我们再聊更高难度的变形。

喜欢的话不如来个关注

以上是关于春招好题 | 动态规划——硬币问题及其变形的主要内容,如果未能解决你的问题,请参考以下文章

最强解析面试题:硬币计算 动态规划

java动态规划取硬币问题

硬币找零问题的动态规划实现

最少硬币找零问题-动态规划

硬币问题-动态规划详解

硬币找零问题之动态规划