啰嗦几句
背包问题难度不高,就那么几个类,自己写一遍代码准没错。
0-1背包问题
对应leetcode:416、474、1049
动态规划解决这个问题。对某一物品,要么放入背包,要么不放,就这两种选择方式。对所有的物品都如此思考一遍后就得到了结论。用递归来实现动态规划肯定是一件不好的事情,慢,且容易栈溢出。
那就用循环呗,还有啥好商量的么?就是事前先定义好dp数组,根据状态数量确定是一维还是二维数组,然后搞就完事儿了。改成循环后,思考的方式就变了,变成:已经得知了第i个的最优解,如何求解第i+1的最优解。
这里如何判断是需要建立几维dp数组呢?很显然,是前多少人能达到多少数量,所以是二维。
另外压缩也了解一下吧,不然dp数组要爆炸了。
总的来说,这道题可以有三种写法,咱们就放最好的一种:压缩后的dp。上代码:
leetcode:416 medium 0-1背包问题
class Solution {
public:
bool canPartition(vector<int>& nums) {
const int length = nums.size();
int sum = 0;
for (int i = 0; i < length; ++i)
sum += nums[i];
if (sum % 2 == 1)
return false;
const int halfSum = sum / 2;
vector<bool> details(halfSum + 1, false);
vector<bool> details_ori(halfSum + 1, false);
for (int i = 0; i < length; ++i)
{
for (int j = 0; j < halfSum + 1; ++j)
{
if (i == 0)
details[j] = (nums[0] == j) ? true : false;
else
{
if(details_ori[j] == true)
details[j] = true;
else if(j == nums[i])
details[j] = true;
else
details[j] = (j > nums[i]) ? details_ori[j-nums[i]] : false;
}
}
for (int k = 0; k < halfSum + 1; ++k)
details_ori[k] = details[k];
}
return details[halfSum];
}
};
我这缩减的还不够彻底其实可以改变一下顺序,将details_ori也取消掉。
完全背包问题
完全背包问题与0-1背包问题的不同之处在于,完全背包中物品的数量可以是任意的。
其实完全背包问题可以很轻易的转换成0-1背包问题。虽然物品的数量是无限的,但是由于背包会限制,所以其实还是个0-1背包问题。
上代码:
leetcode:518 medium 完全背包问题
class Solution {
public:
int change(int amount, vector<int>& coins) {
int coinsSize = coins.size();
if(amount == 0)
return 1;
vector<int> details(amount+1, 0);
for(int i = 0; i < coinsSize; ++ i)
{
details[0] = 1;
for(int j = amount; j >= 1; -- j)
{
if(i == 0)
details[j] = (j%coins[0] == 0) ? 1 : 0;
else
{
int coinsNum = j / coins[i];
for(int p = 0; p < coinsNum; ++ p)
details[j] += details[j-(p+1)*coins[i]];
}
}
}
return details[amount];
}
};
这下是把details_ori也删除掉了,内存消耗是非常小的,只是不知道为啥运行时间这么久。
混合背包
混合背包是在完全背包的基础上发展来的,每个物品的数量定一个常数(其实和完全背包没什么本质上的区别)