春招好题 | 动态规划——硬币问题及其变形
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
代码如下:
#
#
#
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(不用任何钱币是换不了钱的)
上代码:
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种不同的组合,想不到吧!?
今天先到这儿,之后我们再聊更高难度的变形。
喜欢的话不如来个关注
以上是关于春招好题 | 动态规划——硬币问题及其变形的主要内容,如果未能解决你的问题,请参考以下文章