问题描述:java
有数量不限的硬币,币值为25分、10分、5分和1分,请编写代码计算n分有几种表示法。算法
求解思路:数组
这也是典型的动态规划问题,咱们能够这样考虑:当只有1分的硬币时,n从1到n分别有多少种表示方法;当有1分和5分的硬币时,n从1到n分别有多少种表示方法,所以类推,直到咱们将1分、5分、10分和25分的硬币所有使用完。思想相似于0-1背包问题,0-1背包问题的具体求解方法能够参考个人上一篇博客动态规划之0-1背包问题。咱们用数组coins[i]={1,5,10,25}表示各类币值,此时能够维护一张二维表ways[i][j],其中横坐标表示前i种表示币值,j表示硬币的总值,则ways[i][j]表示能用前i种硬币来表示j分的方法数。app
当增长一种新的硬币币值时,有两种状况:测试
(1)不加入此种币值:ways[i][j]=ways[i-1][j];spa
(2)加入此种币值:加入该枚硬币以前的方法数为ways[i][j-coins[i]],那么加入该枚硬币以后构成j分的方法数也为ways[i][j-coins[i]]。.net
所以当增长一种新的币值时,j分的表示方法数为ways[i][j]=ways[i-1][j]+ways[i][j-coins[i]]。code
代码实现:blog
[java] view plain copy递归
固然,维护二维表未免过于复杂,咱们能够维护一张一维表,即用一维数组ways[j]来记录j分的表示方法数。改进的代码实现以下:
[java] view plain copy
问题:要找K元的零钱,零钱的种类已知,保存在数组coins[]中,要求:求出构成K所需的最少硬币的数量和零钱的具体数值。
分析:(1)贪心算法:,先从面额最大的硬币开始尝试,一直往下找,知道硬币总和为N。可是贪心算法不能保证可以找出解(例如,给,2,3,5,而后N=11,致使无解5,5,1)。
(2)动态规划:
思想:快速实现递归(将前面计算的结果保存在数据里,后面重复用的时候直接调用就行,减小重复运算)
动态规划算法与分治法相似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,而后从这些子问题的解获得原问题的解。与分治法不一样的是,适合 于用动态规划求解的问题,经分解获得子问题每每不是互相独立的。若用分治法来解这类问题,则分解获得的子问题数目太多,有些子问题被重复计算了不少次。如 果咱们可以保存已解决的子问题的答案,而在须要时再找出已求得的答案,这样就能够避免大量的重复计算,节省时间。咱们能够用一个表来记录全部已解的子问题 的答案。无论该子问题之后是否被用到,只要它被计算过,就将其结果填入表中。
代码:
/*找零钱问题:要找 K元的零钱,零钱的种类有 coins[],要求零钱的张数最少,用 road[]来找出具体使用的零钱*/ public class MinCount_coins { public static void main (String[] args) { int coins[]={3,5}; int k=4; int road[]=new int[k+1]; int min=getMinCount(k ,coins ,road ); if(min>Integer. MAX_VALUE-k ){ //min 没有另外赋值,则表示零钱不够 System.out. println( "零钱不够!" ); }else{ System.out. println( "数值为" +k +" 时,须要的最少的硬币数为: "+ min); for(int j=k;j>0;){ System.out. print( road[ j]+ "\t"); j=j-road[j]; //j为当前要找的零钱值, road[j]为当前面额下,最近加入的零钱 } } } public static int getMinCount (int k,int c[],int r[]){ int a[]=new int[k+1];//保存最近加入的零钱值 a [0]=0; for(int x=1;x<k+1;x++){ //要求a[k],先求a[1]~a[k-1] if(x>=c[0]){ //给a[x]附初值 a[x]=a[x-c[0]]+1; r[x]=c[0]; }else{ //要找零钱比最小零钱值还小,零钱不够 a[x]=Integer.MAX_VALUE- k; } for(int i=1;i<c.length;i++){ if(x>=c[i]&&(a[x]>a[x-c[i]]+1)){//x-c[i]表示当前值减去coins[]中值,便可由前面那些子问题+1一次得来 a[ x]= a[ x- c[ i]]+1; r[ x]= c[ i]; } } } return a[k]; } }
本文中的解解法综合考虑了各类状况,好比改变了零钱的种类,零钱不够等状况
动态规划的基本思想是将待求解问题分解成若干个子问题,先求解子问题,并将这些子问题的解保存起来,若是之后在求解较大子问题的时候须要用到这些子问题的解,就能够直接取出这些已经计算过的解而免去重复运算。保存子问题的解可使用填表方式,例如保存在数组中。
用一个实际例子来体现动态规划的算法思想——硬币找零问题。
硬币找零问题描述:现存在一堆面值为 V一、V二、V3 … 个单位的硬币,问最少须要多少个硬币才能找出总值为 T 个单位的零钱?假设这一堆面值分别为 一、二、五、2一、25 元,须要找出总值 T 为 63 元的零钱。
很明显,只要拿出 3 个 21 元的硬币就凑够了 63 元了。
基于上述动态规划的思想,咱们能够从 1 元开始计算出最少须要几个硬币,而后再求 2 元、3元…每一次求得的结果都保存在一个数组中,之后须要用到时则直接取出便可。那么咱们何时须要这些子问题的解呢?如何体现出由子问题的解获得较大问题的解呢?
其实,在咱们从 1 元开始依次找零时,能够尝试一下当前要找零的面值(这里指 1 元)是否可以被分解成另外一个已求解的面值的找零须要的硬币个数再加上这一堆硬币中的某个面值之和,若是这样分解以后最终的硬币数是最少的,那么问题就获得答案了。
单是上面的文字描述太抽象,先假定如下变量:
values[] : 保存每一种硬币的币值的数组
valueKinds :币值不一样的硬币种类数量,即values[]数组的大小
money : 须要找零的面值
coinsUsed[] : 保存面值为 i 的纸币找零所需的最小硬币数
算法描述:
当求解总面值为 i 的找零最少硬币数 coinsUsed[ i ] 时,将其分解成求解 coinsUsed[ i – cents]和一个面值为 cents 元的硬币,因为 i – cents < i , 其解 coinsUsed[ i – cents] 已经存在,若是面值为 cents 的硬币知足题意,那么最终解 coinsUsed[ i ] 则等于 coinsUsed[ i – cents] 再加上 1(即面值为 cents)的这一个硬币。
下面用代码实现并测试一下:
测试结果:
面值为 1 的最小硬币数 : 1 面值为 2 的最小硬币数 : 2 面值为 3 的最小硬币数 : 3 面值为 4 的最小硬币数 : 4 面值为 5 的最小硬币数 : 1 面值为 6 的最小硬币数 : 2 ... ... 面值为 60 的最小硬币数 : 3 面值为 61 的最小硬币数 : 4 面值为 62 的最小硬币数 : 4 面值为 63 的最小硬币数 : 3
上面的代码并无给出具体应该是哪几个面值的硬币,这个能够再使用一些数组保存而打印出来。
/**
* 功能:给定数量不限的硬币,币值为25分,10分,5分,1分,计算n分有几种表示法。
*/
[java] view plain copy