【算法】找零钱-动态规划实现过程解析
来源:互联网 发布:vue.js 实现网页下载 编辑:程序博客网 时间:2024/06/06 12:35
本文章素材来自:https://www.nowcoder.com/study/vod/1/12/1
题目要求:
有数组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]; }
- 【算法】找零钱-动态规划实现过程解析
- 找零钱-动态规划
- 动态规划:找零钱问题
- 动态规划 找零钱问题
- 动态规划找零钱问题
- 动态规划算法-----找零钱问题(求最优解)
- 动态规划算法思想解决找零钱问题
- 动态规划——找零钱问题
- 动态规划之找零钱问题
- 动态规划解决找零钱问题
- 动态规划经典联系题----找零钱
- 【动态规划】—找零钱问题
- 动态规划1:找零钱练问题
- 动态规划5:找零钱问题
- 动态规划解决找零钱问题
- 动态规划之找零钱问题
- 数据结构-动态规划之找零钱
- C++实现贪心算法-找零钱
- iOS 11导航栏按钮不显示问题
- const与define的一点区别
- Laravel Redis和Session 存放在不同的数据库
- spring mvc入门
- 笔记:一键分享插件
- 【算法】找零钱-动态规划实现过程解析
- python __name__ = '__main__'的作用
- C# json数据流解析,可解决粘包、断包、坏包问题
- BZOJ 2243 浅谈树链剖分+线段树
- mycat全局表实战应用简析(下):多节点自增字段的处理
- Python实现排序算法
- CSS实现单行、多行文本溢出显示省略号(…)
- 用GridView来显示文件链接
- selenium学习---1