动态规划
动态规划就是消除冗余,直接使用之前算过的值,而不需要再次重新计算的方式。
记忆搜索
1.记忆搜索是某种形态的动态规划。
2.记忆化搜索方法不关心达到目的的路径。
3.动态规划是规定好每一个递归过程的计算顺序,以此计算,后面的计算依赖前面的计算
4.两者都是空间换时间,也都有枚举过程,区别在于动态规划有计算顺序(可以进一步优化),而记忆搜索不用规定。
例子:
给定数组arr,arr中所有的值都为正数且不重复。每个值都代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱总数,求换钱有多少种方法?
arr={5、10、25、1} ,aim=1000。
分析:
1.用0张5元,用剩下的{10、25、1}三种面值的货币置换剩下的1000元的方法数-------------res0
2.用1张5元,用剩下的{10、25、1}三种面值的货币置换剩下的995元的方法数-------------res1
.....................................
201.用200张5元,用剩下的{10、25、1}三种面值的货币置换剩下的0元的方法数-------------res200
使用p(arr,index,aim)返回用数组arr{index,...,N-1}中的所有面值货币组成aim的方法数
暴力搜索:
暴力搜索之所以暴力就是因为其重复了太多的冗余操作,重复计算。试想当使用了2张5元和0张10元后,用{25、1}置换剩下的990的方法数为p(arr,2,990)
而当使用了0张5元和1张10元后,用{25、1}置换剩下的990的方法数也是p(arr,2,990)
这样的情况会大量的存在,也就造成了大量的计算冗余所以不使用。
//暴力搜索
int coins1(int arr[], int length, int aim)
{
std::cout << "arr长度为" << *arr << std::endl;
if (arr ==NULL || length == 0 || aim < 0)
{
return 0;
}
return process1(arr, length ,0 , aim);
}
int process1(int arr[], int length, int index, int aim)
{
int res = 0;
if (index == length)
{
res = aim == 0 ? 1 : 0;
}
else
{
for (int i=0;arr[index]*i<=aim;i++)
{
res += process1(arr,length ,index + 1, aim - arr[index] * i);
}
}
return res;
}
记忆搜索:
迭代的变化量是index和aim,所以可以将index和aim作为key,将每次迭代的结果放置一个map中。当进入新的迭代时,先检查map中是否有此key对应的值,若有则取出value即可,否则便计算后再将key和value一同放入map中。
int coins2(int arr[], int length, int aim)
{
if (arr == NULL || length == 0 || aim < 0)
{
return 0;
}
std::vector<std::vector<int>> Map(length+1,std::vector<int>(aim+1));
return process2(arr, length, 0, aim ,Map);
}
int process2(int arr[], int length, int index, int aim, std::vector<std::vector<int>> Map)
{
int res = 0;
if (index == length)
{
res = aim == 0 ? 1 : 0;
}
else
{
int mapValue = 0;
for (int i=0;arr[index]*i<aim;++i)
{
mapValue = Map[index+1][ aim - arr[index] * i ];
if (mapValue != 0)
{
res += mapValue == -1 ? 0 : mapValue;
}
else
{
res += process2(arr, length, index + 1, aim - arr[index] * i, Map);
}
}
}
Map[index][aim] = res == 0 ? -1 : res;
return res;
}
动态规划:
假设arr长度为N,生成行数为N,列数为aim+1的矩阵dp。
dp[i][j]的含义是在使用arr[0...i]货币的情况下,组成钱数j有多少种方法
则可以知道第一列表示组成钱数为0的方法数为1(即不使用任何面值的货币),第一行为使用arr[0]组成钱数为j的方法数,可知只有当j为arr[0]这个数的整数倍时才有可能用arr[0]组成j。所以第一行在j等于arr[0]的整数倍处为1,其余为0。
d[i][j]可以表示为:
由此可知d[i][j]=dp[i-1][j]+dp[i-1][j-1*arr[i]]+dp[i-1][j-2*arr[i]]+...
可知dp[i][j-1*arr[i]]=dp[i-1][j-1*arr[i]]+dp[i-1][j-2*arr[i]]+...
所以上述过程可以化简为:
d[i][j]=dp[i-1][j]+dp[i][j-1*arr[i]]
所以整个问题得到了化简。
这就是动态规划的方法。
//动态规划
int coins3(int arr[], int length, int aim)
{
if (arr == NULL || length == 0 || aim < 0)
{
return 0;
}
return process3(arr, length-1, aim);
}
int process3(int arr[], int i,int j)
{
if ( i == 0)
{
if ( j%arr[0] ==0 )
{
return 1;
}
else
{
return 0;
}
}
else if ( j == 0 )
{
return 1;
}
else if (i<0 || j<0)
{
return 0;
}
else
{
return (process3(arr, i-1,j) + process3(arr, i , j-arr[i]));
}
}