完全背包问题
题目:给定一个数组Arr,每一个元素表示一种硬币 ,数量无限。现在需要这些种硬币组成我们的目标是aim,问有多少中组合的方法;
Arr = {3,2,1,5,7,3},aim = 20;
-
- 递归解法
寻找可变参数:idx表示从数组中的第idx以后的元素中找到组成rest的组合数;rest的初始值为aim;这类递归问题可以想成是一颗树;
f(idx,rest)表示数组Arr的第idx以后的元素集合,从这个集合中选取硬币组成rest的组合数;
- 递归解法
-
code
//表示从数组种的idx位置开始以后种类的货币组成rest有多少种方法
int get_N1(vector<int>& Arr, int idx, int rest)
{
if (idx == Arr.size())
return rest == 0 ? 1 : 0;
int res = 0;
for (int k = 0; k * Arr[idx] <= rest; k++)
{
res += get_N1(Arr, idx + 1, rest - Arr[idx] * k);
}
return res;
}
for循环的意义就是一个节点从所有的孩子拿信息,只不过这里的孩子说是根据给定的Arr已经aim才能确定,所以写成这种形式;
举例说明:Arr=[3,4,2,34,7,23],aim=20
面值3的硬币使用0次,get_N1(Arr,1,aim)
面值3的硬币使用1次,get_N1(Arr,1,aim-31)
面值3的硬币使用2次,get_N1(Arr,1,aim-32)
面值3的硬币使用6次,get_N1(Arr,1,aim-3*6)
使用7次,rest就小于零了;并且一直使用面值3,不能达到aim;
-
- 严格表结构
/严格表结构
int get_N2(vector<int>& Arr, int idx, int rest)
{
vector<vector<int>> dp(Arr.size() + 1, vector<int>(rest+1, 0));
dp[Arr.size()][0] = 1;
for (int i = Arr.size() - 1; i >= 0;i--)
{
for (int j = 0; j <= rest; j++)
{
/*int k = j;
while (k >= 0)
{
dp[i][j] = dp[i+1][k];
k -= Arr[i+1];
}*/
int res = 0;
for (int k = 0; k * Arr[idx] <= rest; k++)
{
res += dp[i + 1][rest - Arr[i] * k];
}
dp[i][j] += res;
}
}
return dp[idx][rest];
}
-
- 优化版本
复用dp数组的信息
- 优化版本
/严格表结构
int get_N2(vector<int>& Arr, int idx, int rest)
{
vector<vector<int>> dp(Arr.size() + 1, vector<int>(rest+1, 0));
dp[Arr.size()][0] = 1;
for (int i = Arr.size() - 1; i >= 0;i--)
{
for (int j = 0; j <= rest; j++)
{
dp[i][j] = dp[i + 1][j];
if (j - Arr[i] >= 0)
{
if (j - Arr[i] >= 0)
dp[i][j] += dp[i][j - Arr[i]];
}
}
}
return dp[idx][rest];
}
- 心得
动态规划的题目重要是找到尝试的思路,也就是递归版本;可以i尝试左右边界,取值范围等;找到可变参数是解题的关键,可变参数越少越好,可变参数最好是一个值,不要使用数组类型或者其他类型的可变参数,最后确定可变参数的范围