递归和动态规划(一)
来源:互联网 发布:廖雪峰python教程博客 编辑:程序博客网 时间:2024/05/16 09:51
换钱的方法数
题目:给定数组arr, arr中所有的值都为正数且不重复。每个值代表一种面值货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求换钱有多少种方法。
解题思路:
解法一:暴力递归
如果arr={5,10,25,1}, aim=1000,过程如下:
1. 用0张5元的货币,让[10, 25, 1]组成剩下的1000,最终方法数记为res1
2..用1张5元的货币,让[10, 25, 1]组成剩下的995,最终方法数记为res2
3. 用3张5元的货币,让[10, 25, 1]组成剩下的990,最终方法数记为res3
...
201. 用200张5元的货币,让[10, 25, 1]组成剩下的0,最终方法数记为201
那么res1 + res2 + ...+res201 的值就是总的方法数。
具体代码实现如下:
int coins1(vector<int> arr, int aim){if(arr.size() == 0 || aim < 0)return 0;return process1(arr, 0, aim);}int process1(vector<int> arr, int index, int aim){int res = 0;if(index == arr.size())res = aim == 0 ? 1 : 0;elsefor(int i=0; arr[index]*i <= aim; i++)res += process1(arr, index+1, aim - i*arr[index]);return res;}解法二:记忆化搜索
针对暴力递归存在大量重复搜索的情况,可以事先准备一个m,每计算完一个递归过程,都将结果记录到m中,当下次进行同样的递归过程之前,先m中查询这个递归过程是否已经计算过,如果已经计算过,就把值拿出来直接用,如果没有计算过,需要再进入递归过程。
m[i][j]表示递归过程p(i, j)的返回值。
m[i][j] = 0表示递归过程p(i, j)从来没有计算过。
m[i][j] = -1 表示递归过程p(i, j)计算过,但返
回值是0.如果m[i][j]的值既不等于0,也不等于-1,记为a,则表示递归过程p(i, j)返回值为a
具体代码如下:
int coins2(vector<int> arr, int aim){if(arr.size() == 0 || aim < 0)return 0;vector<vector<int> > m(arr.size()+1, vector<int>(aim+1, 0));return process2(arr, 0, aim, m);}int process2(vector<int> arr, int index, int aim, vector<vector<int> >& m){int res = 0;if(index == arr.size())res = aim == 0 ? 1 : 0;else{int mapValue = 0;for(int i=0; i * arr[index] <= aim; i++){mapValue = m[index+1][aim-i*arr[index]];if(mapValue != 0)res += mapValue == -1 ? 0 : mapValue;elseres += process2(arr, index+1, aim-i*arr[index], m);}}m[index][aim] = res == 0 ? -1 : res;return res;}
解法三:动态规划
生成行数为N列数为aim+1的矩阵dp,dp[i][j]表示在使用arr[0...i]货币的情况下,组成钱数j的方法数。
- 对于dp第一列的值dp[...][0], 表示组成钱数0的方法数,只有一种,就是不使用任何货币
- 对于dp第一行的值dp[0][...],表示只使用arr[0]这一种货币的情况下,组成钱的方法数
- 其他位置(i, j)的dp值是以下几个值的累加。
- 完全不用arr[i]货币,只使用arr[0...i-1]货币时,方法数为dp[i-1][j]
- 用1张arr[i]货币,剩下的钱用arr[0...i-1]货币组成时,方法数为dp[i-1][j-arr[i]]
- 用2张arr[i]货币,剩下的钱用arr[0...i-1]货币组成时,方法数为dp[i-1][j-2*arr[i]].
- 用k张arr[i]货币,剩下的用arr[0...i-1]货币组成时,方法数为dp[i-1][j-2*arr[i]].
- .............
具体代码如下:
int coins3(vector<int> arr, int aim){if(arr.size() == 0 || aim < 0)return 0;vector<vector<int> > dp(arr.size(), vector<int>(aim+1, 0));for(int i=0; i<arr.size(); i++)dp[i][0] = 1;for(int j=1; j*arr[0]<=aim; j++)dp[0][arr[0] * j] = 1;int num = 0;for(int i=1; i<arr.size(); i++){for(int j=1; j<=aim; j++){num = 0;for(int k=0; j - k*arr[i] >= 0; k++)num += dp[i-1][j - k*arr[i]];dp[i][j] = num;}}return dp[arr.size()-1][aim];}
解法四:动态规划(改进解法三)
解法三,第1种情况的方法数就是dp[i-1][j], 而第2种情况一直到第k种情况的方法累加值其实就是dp[i][j-arr[i]]
则dp[i][j] = dp[i-1][j] + dp[i][j-arr[i]]
具体代码如下:
int coins4(vector<int> arr, int aim){if(arr.size() == 0 || aim < 0)return 0;vector<vector<int> > dp(arr.size(), vector<int>(aim+1, 0));for(int i=0; i<arr.size(); i++)dp[i][0] = 1;for(int j=1; j*arr[0]<=aim; j++)dp[0][j*arr[0]] = 1;for(int i=1; i<arr.size(); i++)for(int j=1; j<=aim; j++){dp[i][j] += dp[i-1][j];dp[i][j] += j-arr[i] >= 0 ? dp[i][j-arr[i]] : 0;}return dp[arr.size()-1][aim];}
解法五:动态规划空间压缩
int coins5(vector<int> arr, int aim){if(arr.size() == 0 || aim < 0)return 0;vector<int> dp(aim+1, 0);for(int j=1; j*arr[0]<=aim; j++)dp[j*arr[0]] = 1;for(int i=1; i<arr.size(); i++)for(int j=1; j<=aim; j++)dp[j] += j - arr[i] >= 0 ? dpj-arr[i]] : 0;return dp[aim];}
注:算法思路参考程序员代码面试指南,由本人自己改由C++实现!
- 递归和动态规划(一)
- 问题一、走台阶问题(递归和动态规划)
- 动态规划和递归
- 递归和动态规划
- 动态规划和递归
- 递归和动态规划
- 递归和动态规划
- 递归和动态规划
- 递归和动态规划(1)
- 9.9递归和动态规划(一)——小孩上楼梯的方式的种类
- 递归和动态规划专题(一)----剑指offer+左程云算法
- 【动态规划】数字三角形最大值(一)(递归)
- 动态规划(一) —— 递归求解
- 最长回文子串(动态规划和递归)
- 递归和动态规划的算法题(1)
- scramble string (使用动态规划和递归做)
- N皇后问题(递归和动态规划)
- 爬台阶问题(递归和动态规划实现)
- bzoj 3550: [ONTAK2010]Vacation (单纯形)
- Kickstart Round A 2017 Problem A. Square Counting 公式、数论逆元、除法取模
- 入门机器学习,我们都要学什么?
- 前端性能优化
- 简易词典
- 递归和动态规划(一)
- 欢迎使用CSDN-markdown编辑器
- 注释转换
- SPOJ BLMIRINA 计算几何
- 排序--2.希尔
- 10、虚拟机安装centos 及安装java
- Node的简单读取文件内容
- 0306 HTML&CSS
- Codeforces Round #403 (Div. 2, based on Technocup 2017 Finals) C (dfs+vector)