版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
1. 问题描述
目前市面上的纸币主要有1元,3元,5元三种,如果要组成50元,有多少种货币组成方式?
2. 动态规划
2.1 基本思想
问题的最优解如果可以由子问题的最优解推导得到,则可以先求解子问题的最优解,再构造原问题的最优解;若子问题有较多的重复出现,则可以自底向上从最终子问题向原问题逐步求解。
2.2 使用条件
可分为多个相关子问题,子问题的解被重复使用
- Optimal substructure(优化子结构):
- 一个问题的优化解包含了子问题的优化解
- 缩小子问题集合,只需那些优化问题中包含的子问题,降低实现复杂性
- 可以自下而上
- Subteties(重叠子问题)
- 在问题的求解过程中,很多子问题的解将被多次使用
2.3 设计步骤
- 分析优化解的结构
- 递归地定义最优解的代价
- 自底向上地计算优化解的代价保存之,并获取构造最优解的信息
- 根据构造最优解的信息构造优化解
2.4 特点
- 把原始问题划分成一系列子问题
- 求解每个子问题仅一次,并将其结果保存在一个表中,以后用到时直接存取,不重复计算,节省计算时间
- 自底向上地计算
- 整体问题最优解取决于子问题的最优解(状态转移方程)(将子问题称为状态,最终状态的求解归结为其他状态的求解)
3. 解题思路
希望用m张纸币构成sum元,而(x1,x2,x3…)分别代表1元、3元、5元。。。
得到推导公式:
sum=x1∗V1+x2∗V2+…+xm∗Vm
根据最后一个面额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
假设:
dp[i][sum] = 用前i种硬币构成sum 的所有组合数。
那么题目的问题实际上就是求dp[m][sum],即用前m种纸币(所有纸币)构成sum的所有组合数。
在上面的联合等式中:
当xm=0时,有多少种组合呢?
实际上就是前i-1种纸币组合sum,有dp[i-1][sum]种!
xm = 1 时呢,有多少种组合?
实际上是用前i-1种纸币组合成(sum - Vm)的组合数,有dp[i-1][sum -Vm]种;
xm =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
4. 源代码
摆弄了半天也没用DP求出各种组合的情况,不过求出了组合数:
public static void test(int money){
int change[]={1,3,5}; //零钱
int dp[] = new int[money+1];
dp[0] = 1;
for(int i = 0;i < change.length;++i){
for(int j = change[i];j <= money;++j){
dp[j] =(dp[j]+dp[j-change[i]]);
}
}
System.out.println(dp[money]);
}
public static void main(String[] args) {
test(50);
}
还是用深度优先搜索求出了所有组合的情况:
package com.company.algorithm;
/**
* @author renwen
*/
public class DFS {
static int count=0; //用于统计所有的组合数
final static int[] value = {1,3,5}; //硬币面值
static int[] num = new int[10]; //每种面值的纸币的数量
static int sum = 50; //总共的钱数
/**
*
* @param n 现在的钱数
* @param k 代表遍历每一种面值的纸币
*/
private static void dfs(int n, int k) {
//边界条件,遍历到最后一种面值的纸币时:
if(k == value.length) {
if(n == sum) {//==50 符合要求 进行输出
count++;
System.out.println("第" + count + "种情况:");
for(int i=0; i<value.length; ++i) {
System.out.println(value[i]+": "+num[i]+" ");
}
return ;
}
return ;
}
for(int i=0; i<=sum/value[k];i++) {// i表示value[i] 这种纸币的数量
num[k]=i;
dfs( n+i*value[k], k+1);//继续进行搜索
num[k]=0;//恢复初始状态
}
return ;
}
public static void main(String[] args) {
dfs(0,0);
System.out.println(count);
}
}