1元,3元,5元组合成50元的货币组成方式

1. 问题描述

目前市面上的纸币主要有1元,3元,5元三种,如果要组成50元,有多少种货币组成方式?

2. 动态规划

2.1 基本思想

问题的最优解如果可以由子问题的最优解推导得到,则可以先求解子问题的最优解,再构造原问题的最优解;若子问题有较多的重复出现,则可以自底向上从最终子问题向原问题逐步求解。

2.2 使用条件

可分为多个相关子问题,子问题的解被重复使用

  1. Optimal substructure(优化子结构):
  • 一个问题的优化解包含了子问题的优化解
  • 缩小子问题集合,只需那些优化问题中包含的子问题,降低实现复杂性
  • 可以自下而上
  1. Subteties(重叠子问题)
  • 在问题的求解过程中,很多子问题的解将被多次使用

2.3 设计步骤

  1. 分析优化解的结构
  2. 递归地定义最优解的代价
  3. 自底向上地计算优化解的代价保存之,并获取构造最优解的信息
  4. 根据构造最优解的信息构造优化解

2.4 特点

  1. 把原始问题划分成一系列子问题
  2. 求解每个子问题仅一次,并将其结果保存在一个表中,以后用到时直接存取,不重复计算,节省计算时间
  3. 自底向上地计算
  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);
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值