递归和动态规划(一)

来源:互联网 发布:廖雪峰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的方法数。

  1. 对于dp第一列的值dp[...][0], 表示组成钱数0的方法数,只有一种,就是不使用任何货币
  2. 对于dp第一行的值dp[0][...],表示只使用arr[0]这一种货币的情况下,组成钱的方法数
  3. 其他位置(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]].
  • .............
        4 .最终dp[N-1][aim]的值就是最终结果

具体代码如下:

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++实现!


2 0
原创粉丝点击