【算法】找零钱-动态规划实现过程解析
2017-09-29 12:07
591 查看
本文章素材来自:https://www.nowcoder.com/study/vod/1/12/1
题目要求:
假设测试数据如下:
int[] penny=new int[]{5, 15, 25, 1};
int aim=1000;
因此可以定义递归过程:
具体的算法在这里就不贴出来来,可以参考第2点的算法实现。
因为暴力求解时使用的递归,因此会存在重复的计算,因此在原基础上使用一个数据结构将已经计算好的结果保存起来,如果需要再次计算时就直接从里面取出结果,这样就可以避免递归的重复计算。
因此,在上述代码中,使用来一个
O(n * aim^2)
由记忆搜索法与其引出的动态规划法的核心算法的比较,发现并无太大的差异,而主要的区别就是如上图所说的,动态规划规定好了计算的顺序(计算
根据上图的可以知道,需要累加的项只有
注意:为什么说只有部分项需要累加呢,这因为这些项之间都是依次相差 (arr[i]-1)个位置,而两个项中间的元素的值,其实是为0。举例说明,因为
题目要求:
有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim(小于等于1000)代表要找的钱数,求换钱有多少种方法。 给定数组penny及它的大小(小于等于50),同时给定一个整数aim,请返回有多少种方法可以凑成aim。
假设测试数据如下:
int[] penny=new int[]{5, 15, 25, 1};
int aim=1000;
1、暴力求解法(利用递归的思想)
实现思想:根据上面的测试的数据,有如下想法因此可以定义递归过程:
具体的算法在这里就不贴出来来,可以参考第2点的算法实现。
2、记忆化搜索法(在暴力求法的基础上修改的)
public static int countWays(int[] penny, int aim) { // write code here if (penny == null || penny.length == 0 || aim < 0) return 0; else { HashMap<String, Integer> map = new HashMap<>(); return fun( map,penny, 0, aim); } } public static int fun(HashMap<String,Integer> map, int[] arr, int index, int aim) { if (index == arr.length) { //如果aim刚好为0了,就表示找到了一个方案,否则penng数组中的币值用完了aim还有剩余就表示方案行不通,即没有对应的方案 return aim == 0 ? 1 : 0; } String key = index + "-" + aim; if (map.containsKey(key)) return map.get(key); int res = 0; for (int i = 0; arr[index] * i <= aim; i++) res += fun(map, arr, index + 1, aim - arr[index] * i); map.put(key, res); return res; }
因为暴力求解时使用的递归,因此会存在重复的计算,因此在原基础上使用一个数据结构将已经计算好的结果保存起来,如果需要再次计算时就直接从里面取出结果,这样就可以避免递归的重复计算。
因此,在上述代码中,使用来一个
HashMap来存储计算好的结果,其中Map的key是由
index和
aim组成的,对应的
value即为已经计算过的结果。
3、由 记忆化搜索引出的 动态规划
O(n * aim^2)
public static int countWays(int[] penny, int aim) { if (penny == null || penny.length == 0 || aim < 0) return 0; int dp[][]=new int[penny.length][aim+1]; for (int i = 0; i < penny.length; i++) dp[i][0] = 1; for (int i = 1; i <= aim; i++) { if (i % penny[0] == 0) dp[0][i] = 1; else dp[0][i] = 0; } for (int i = 1; i < penny.length; i++) { for (int j = 1; j <= aim; j++) { int count = 0; for (int k = 0; penny[i]*k <= j; k++) count += dp[i-1][j-penny[i]*k]; dp[i][j] = count; } } return dp[penny.length-1][aim]; }
由记忆搜索法与其引出的动态规划法的核心算法的比较,发现并无太大的差异,而主要的区别就是如上图所说的,动态规划规定好了计算的顺序(计算
dp[i][j]就要先计算出
dp[i-1][0 ~ j]的结果,然后在枚举求和,而
dp[i-1][0 ~ j]每个元素的结果又需要由对应的上一排的枚举求和实现…),而记忆搜索法本质还是递归,只不过优化了其过程,避免的重复的递归计算。
4、优化后的动态规划
根据上图的可以知道,需要累加的项只有
dp[i-1][j-1*arr[i]]以及其所在那一排的且位于它前面的某些项(这一部分就相当于
dp[i][j-arr[i]),以及
dp[i-1][i]这一项。因此可以优化原有动态规划的实现,避免上一排枚举求和的操作。
注意:为什么说只有部分项需要累加呢,这因为这些项之间都是依次相差 (arr[i]-1)个位置,而两个项中间的元素的值,其实是为0。举例说明,因为
dp[i-1][j-1*arr[i]](表示使用1 张arr[i]货币),到
dp[i-1][j]之间(表示完全不用使用arr[i]货币),其中包含了
j - arr[i]+1、
j - arr[i]+2…、、
j - arr[i]+(arr[i]-1),而这些是凑不齐1张arr[i]货币的,因此会有(零钱)剩余,而剩余就表示该方案行不通,即为0。
public static int countWays(int[] penny, int aim) { if (penny == null || penny.length == 0 || aim < 0) return 0; int dp[][]=new int[penny.length][aim+1]; for (int i = 0; i < penny.length; i++) dp[i][0] = 1; for (int i = 1; i <= aim; i++) { if (i % penny[0] == 0) dp[0][i] = 1; else dp[0][i] = 0; } for (int i = 1; i < penny.length; i++) { for (int j = 1; j <= aim; j++) { if (j < penny[i]) dp[i][j] = dp[i-1][j]; else dp[i][j] = dp[i-1][j] + dp[i][j-penny[i]]; } } return dp[penny.length-1][aim]; }
相关文章推荐
- 二叉树解析实现逆波兰公式算法
- pytorch学习笔记(十三):backward过程的底层实现解析
- 多级树集合分裂(SPIHT)算法的过程详解与Matlab实现(7)解码过程——扫描解码
- 贪心算法实例:找零钱(Java实现)
- WebGIS实现在线要素编辑之ArcGIS Server 发布Feature Service 过程解析
- Java图像灰度化的实现过程解析
- 【转】WebGIS实现在线要素编辑之ArcGIS Server 发布Feature Service 过程解析
- 使用存储过程实现进销存系统中的先进先出算法(1)——数据库与存储过程设计
- [开源学习]SwipeMenuListView源码实现过程解析
- 深入解析js轮播插件核心代码的实现过程
- 算法分析之归并排序——算法整体实现过程
- Spring源码分析----IOC容器的实现(IoC容器的初始化过程(定位、载入解析、注册))
- 位运算实现ACL授权与认证过程的原理解析
- 多级树集合分裂(SPIHT)算法的过程详解和Matlab实现(2)数学表述
- Openck_Swift源码分析——增加、删除设备时算法具体的实现过程
- matlab黄金分割算法实现与解析
- 解析左右值无限分类的实现算法
- C语言变量赋值语句的语法解析算法实现
- 作业排程算法—动态规划实现
- AOP实现之配置文件解析过程(二)