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

一,问题描述

给定一组硬币数,找出一组最少的硬币数,来找换零钱N。

比如,可用来找零的硬币为: 1、3、4   待找的钱数为 6。用两个面值为3的硬币找零,最少硬币数为2。而不是 4,1,1

因此,总结下该问题的特征:①硬币可重复多次使用。②在某些情况下,该问题可用贪心算法求解。具体可参考:某种 找换硬币问题的贪心算法的正确性证明

二,动态规划分析

为了更好的分析,先对该问题进行具体的定义:将用来找零的硬币的面值存储在一个数组中。如下:

coinsValues[i] 表示第 i 枚硬币的面值。比如,

第 i 枚硬币     面值

1                1

2                3

3                4

待找零的钱数为 n (上面示例中 n=6)

为了使问题总有解,一般第1枚硬币的面值为1

考虑该问题的最优子结构:设 c[i,j]表示 可用第 0,1,.... i 枚硬币 对 金额为 j 的钱 进行找钱 所需要的最少硬币数。

i 表示可用的硬币种类数, j 表示 需要找回的零钱

第 i 枚硬币有两种选择:用它来找零 和 不用它找零。因此,c[i,j]的最优解如下:

c[i,j]= min{c[i-1,j] , c[i, j-coinsValues[i]] + 1}   其中,

c[i-1,j] 表示 使用第 i 枚硬币找零时,对金额为 j 进行找钱所需要的最少硬币数

c[i, j-coinsValues[i]] + 1 表示 使用 第 i 枚硬币找零时,对金额为 j 进行找钱所需要的最少硬币数。由于用了第 i 枚硬币,故使用的硬币数量要增1

c[i,j] 取二者的较小值的那一个。

另外,对特殊情况分析(特殊情况1)一下:

c[0][j]=Integer.MAXVALUE ,因为 对 金额为 j 的钱找零,但是可以的硬币面值 种类为0,这显然是无法做到的嘛(除非是强盗:) )

c[i][0]=0,因为,对 金额为0的钱 找零,可用来找零的硬币种类有 i 种,金额为0啊,怎么找啊,找个鸭蛋啊。

其实,边界条件是根据具体的问题而定的。DP太博大了,理解的不深。

另外,还有一种特殊情况(特殊情况2),就是: coinsValues[i] > j

这说明第 i 枚硬币的面值大于 金额 j ,这也不能用 第 i 枚硬币来找零啊,(欠你5块钱,但是找你一张百元大钞)不然就亏了了(吃亏是福啊^~^...or 杀鸡焉用牛刀!!!)

有了这个特征转移方程:c[i,j]= min{c[i-1,j] , c[i, j-coinsValues[i]] + 1}  ,就好写代码了。

三,JAVA代码实现

 1     /**
 2      *
 3      * @param coinsValues 可用来找零的硬币 coinsValues.length是硬币的种类
 4      * @param n 待找的零钱
 5      * @return 最少硬币数目
 6      */
 7     public static int charge(int[] coinsValues, int n){
 8         int[][] c = new int[coinsValues.length + 1][n + 1];
 9
10         //特殊情况1
11         for(int i = 0; i <= coinsValues.length; i++)
12             c[i][0] = 0;
13         for(int i = 0; i <= n; i++)
14             c[0][i] = Integer.MAX_VALUE;
15
16         for(int j_money = 1; j_money <=n; j_money++)
17         {
18
19             for(int i_coinKinds = 1; i_coinKinds <= coinsValues.length; i_coinKinds++)
20             {
21                 if(j_money < coinsValues[i_coinKinds-1])//特殊情况2,coinsValues数组下标是从0开始的,  c[][]数组下标是从1开始计算的
22                 {
23                     c[i_coinKinds][j_money] = c[i_coinKinds - 1][j_money];//只能使用 第 1...(i-1)枚中的硬币
24                     continue;
25                 }
26
27                 //每个问题的选择数目---选其中较小的
28                 if(c[i_coinKinds - 1][j_money] < (c[i_coinKinds][j_money - coinsValues[i_coinKinds-1]] +1))
29                     c[i_coinKinds][j_money] = c[i_coinKinds - 1][j_money];
30                 else
31                     c[i_coinKinds][j_money] = c[i_coinKinds][j_money - coinsValues[i_coinKinds-1]] +1;
32             }
33         }
34         return c[coinsValues.length][n];
35     }

①第28行-20行 就是状态转换方程的表示。

②第16行-第19行的for循环体现就是动态规划的自底向上的思想。

复杂度分析:从代码19-20行的for循环来看,时间复杂度为O(MN),M为可用的硬币种类数目,N为待找的零钱金额

从理论上分析,DP(Dynamic Programming)的时间复杂度为子问题的个数乘以每个子问题的可用选择数。显然,这个有MN个子问题,每个子问题有两种选择(选第i枚硬币和不选第i枚硬币)。

一直很好奇DP通过列一个方程就把一个问题给解决了,其实从16-19行的for循环来看,循环的下标是由小到大,说明它先解决子问题,然后再把原问题给解决了。

四,参考资料

某种 找换硬币问题的贪心算法的正确性证明

从 活动选择问题 看动态规划和贪心算法的区别与联系

http://haolloyin.blog.51cto.com/1177454/352115

五,完整代码

import java.util.Arrays;

public class DPCharge {

    /**
     *
     * @param coinsValues 可用来找零的硬币 coinsValues.length是硬币的种类
     * @param n 待找的零钱
     * @return
     */
    public static int charge(int[] coinsValues, int n){
        int[][] c = new int[coinsValues.length + 1][n + 1];

        //特殊情况1
        for(int i = 0; i <= coinsValues.length; i++)
            c[i][0] = 0;
        for(int i = 0; i <= n; i++)
            c[0][i] = Integer.MAX_VALUE;

        for(int j_money = 1; j_money <=n; j_money++)
        {
            for(int i_coinKinds = 1; i_coinKinds <= coinsValues.length; i_coinKinds++)
            {
                if(j_money < coinsValues[i_coinKinds-1])//特殊情况2
                {
                    c[i_coinKinds][j_money] = c[i_coinKinds - 1][j_money];
                    continue;
                }

                //每个问题的选择数目---选其中较小的
                if(c[i_coinKinds - 1][j_money] < (c[i_coinKinds][j_money - coinsValues[i_coinKinds-1]] +1))
                    c[i_coinKinds][j_money] = c[i_coinKinds - 1][j_money];
                else
                    c[i_coinKinds][j_money] = c[i_coinKinds][j_money - coinsValues[i_coinKinds-1]] +1;
            }
        }
        return c[coinsValues.length][n];
    }

    public static void main(String[] args) {
        int[] coinsValues = {1,3,4};
        Arrays.sort(coinsValues);//需要对数组排序,不然会越界.....
        int n = 6;
        int minCoinsNumber = charge(coinsValues, n);
        System.out.println(minCoinsNumber);
    }
}
时间: 2024-08-05 19:32:31

硬币找零问题的动态规划实现的相关文章

硬币找零问题之动态规划

今天我们看一下动态规划的硬币找零问题,主要通过一系列编程题分析动态规划的规律,只要掌握这一规律,许多动态规划的相关问题都可以类比得到. 题目1:给定数组arr,arr中所有的值都是正数且不重复.每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求组成aim的最少货币数. 举例: arr[5,2,3],aim=20.  4张5元可以组成20元,其他的找钱方案都要使用更多张的货币,所以返回4. 题解: 一眼看去这道题好像可以用贪心算法可解,但是仔细分析发现有

动态规划求解最多有几种方案求解硬币找零问题

一,问题描述 假设有 m 种面值不同的硬币,存储在 coinsValues数组中,现需要使用这些硬币来找钱,各种硬币的使用个数不限. 求对于给定的钱数N,我们最多有几种不同的找钱方式.硬币的顺序并不重要. 二,动态规划分析 为了更好的分析,先对该问题进行具体的定义:将用来找零的硬币的面值存储在一个数组中.如下: coinsValues[i] 表示第 i 枚硬币的面值.比如, 第 i 枚硬币     面值 1                1 2                3 3       

硬币找零-记忆化搜索(DP动态规划)

硬币找零 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 在现实生活中,我们经常遇到硬币找零的问题,例如,在发工资时,财务人员就需要计算最少的找零硬币数,以便他们能从银行拿回最少的硬币数,并保证能用这些硬币发工资. 我们应该注意到,人民币的硬币系统是 100,50,20,10,5,2,1,0.5,0.2,0.1,0.05, 0.02,0.01 元,采用这些硬币我们可以对任何一个工资数用贪心算法求出其最少硬币数. 但不幸的是: 我们可能没有这样一种好的硬币系统, 因此

硬币找零问题

硬币找零问题一个经典问题,也是阐述动态规划法几乎必讲的一个例子. 硬币找零问题描述:现存在一堆面值为 V1.V2.V3 - 个单位的硬币, 各单位的硬币数目不限, 问最少需要多少个硬币才能找出总值为 T 个单位的零钱? 比如: 假设这一堆面值分别为 1.2.5.21.25 元,需要找出总值 T 为 63 元的零钱. 基于动态规划的思想,我们可以从目标值为 1 元开始计算出最少需要几个硬币,然后再求 2 元.3元- 每一次求得的结果都保存在一个数组中,以后需要用到时则直接取出即可. #includ

DP:硬币找零

在现实生活中,我们经常遇到硬币找零的问题,例如,在发工资时,财务人员就需要计算最少的找零硬币数,以便他们能从银行拿回最少的硬币数,并保证能用这些硬币发工资.我们应该注意到,人民币的硬币系统是100,50,20,10,5,2,1,0.5,0.2,0.1,0.05,0.02,0.01元,采用这些硬币我们可以对任何一个工资数用贪心算法求出其最少硬币数. 但不幸的是:我们可能没有这样一种好的硬币系统,因此用贪心算法不能求出最少的硬币数,甚至有些金钱总数还不能用这些硬币找零.例如,如果硬币系统是40,30

nyoj995硬币找零(dp完全背包)

硬币找零 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 在现实生活中,我们经常遇到硬币找零的问题,例如,在发工资时,财务人员就需要计算最少的找零硬币数,以便他们能从银行拿回最少的硬币数,并保证能用这些硬币发工资. 我们应该注意到,人民币的硬币系统是 100,50,20,10,5,2,1,0.5,0.2,0.1,0.05, 0.02,0.01 元,采用这些硬币我们可以对任何一个工资数用贪心算法求出其最少硬币数. 但不幸的是: 我们可能没有这样一种好的硬币系统, 因此

NYOJ 995 硬币找零

硬币找零 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 在现实生活中,我们经常遇到硬币找零的问题,例如,在发工资时,财务人员就需要计算最少的找零硬币数,以便他们能从银行拿回最少的硬币数,并保证能用这些硬币发工资. 我们应该注意到,人民币的硬币系统是 100,50,20,10,5,2,1,0.5,0.2,0.1,0.05, 0.02,0.01 元,采用这些硬币我们可以对任何一个工资数用贪心算法求出其最少硬币数. 但不幸的是: 我们可能没有这样一种好的硬币系统, 因此

硬币找零&&爬楼梯&&猴子摘香蕉

硬币找零&&爬楼梯&&猴子摘香蕉 假设有几种硬币,如1.3.5,并且数量无限.请找出能够组成某个数目的找零所使用最少的硬币数. #include"CoinProblem.h" #include<iostream> int countNum=MAX; void CoinProblem(int *coin,int Length,int Value,int count){ if(Value==0){ if(countNum>count){ c

算法笔记——硬币找零之最少硬币数

题目来源:NYOJ995 问题描述: 在现实生活中,我们经常遇到硬币找零的问题,例如,在发工资时,财务人员就需要计算最少的找零硬币数,以便他们能从银行拿回最少的硬币数,并保证能用这些硬币发工资. 我们应该注意到,人民币的硬币系统是 100,50,20,10,5,2,1,0.5,0.2,0.1,0.05, 0.02,0.01 元,采用这些硬币我们可以对任何一个工资数用贪心算法求出其最少硬币数. 但不幸的是: 我们可能没有这样一种好的硬币系统, 因此用贪心算法不能求出最少的硬币数,甚至有些金钱总数还