01背包问题
题目:
文章链接:代码随想录 (programmercarl.com)
视频链接:带你学透0-1背包问题!
动规五部曲:
1.确定dp数组和下标的含义。
这道题需要构建一个二维数组来记录最大价值。dp[i][j]代表任意物品0到物品i,放进容量为j的背包,产生的最大价值。
vector<int> weight={1,3,4};
vector<int> value={15.20.30};
int bagweight=4;
vector<vector<int>> dp(weight.size(),vector<int>(bagweight+1,0));
2.确定递推公式。
dp[i][j]代表任意物品0到物品i,放进容量为j的背包,产生的最大价值。那么如何求dp[i][j]呢?
我们有两个选择,就是装不装物品i,
如果不装物品i,那么dp[i][j]就等于dp[i-1][j]。
如果装物品i,那么dp[i][j]就等于dp[i-1][j-weight[i]]+value[i]。
那究竟装不装物品i,就取决于哪个最后的值大。当然,如果j<weight[i],那肯定不能装i,dp[i][j]只能等于dp[i-1][j]。
代码如下:
if(j<weight[i]) dp[i][j]=dp[i-1][j];
else{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
}
3.dp数组初始化
初始化都是根据递推公式回推出来的,这道题用图像来看会更简单些。
如果我想知道dp[1][1],根据递推公式我们需要知道dp[0][1]和dp[0][0],从图上看出,其实就是当前格子的正上格子和左上格子。所以我们需要对第一行和第一列进行初始化。
第一列很简单,容量为0的包,肯定装不了啥东西,dp[i][0]一定为0.
第一行,只有当我的包容量比物品0大时,才能存东西。
代码如下:
for(int j=0;j<=bagweight;j++)
{
if(j>=weight[0]) dp[0][j]=value[0];
}
4.遍历顺序
这里不管是先遍历背包还是先便利物品都可以,因为按照递推公式,我们每次都需要知道它的上一层的两个数字,而不管是从上到下遍历,还是从左往右遍历,都会先搞定它需要的两个数字。
for(int i = 1; i < weight.size(); i++) { // 遍历物品
for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
}
}
5.举例推导dp数组。
总代码如下:
void test_2_wei_bag_problem1() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagweight = 4;
// 二维数组
vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
// 初始化
for (int j = weight[0]; j <= bagweight; j++) {
dp[0][j] = value[0];
}
// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
cout << dp[weight.size() - 1][bagweight] << endl;
}
int main() {
test_2_wei_bag_problem1();
}
当然这道题也不是一定需要用二维的dp数组,运用滑动数组也可以解决本题。
既然dp[i][j]可以由dp[i-1][j]和dp[i-1][j-weight[i]]来表示,那代表二维数组的每一层都可以由它的上一层求出来,那么就是一维数组的变换。
1.确定dp数组和下标的含义
dp[j]代表容量为j的背包,所背物品的最高价值
2.确定递推公式。
公式思路不变。
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
3.dp数组的初始化
看一下递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
dp数组在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。
4.确认遍历顺序
一维数组,在遍历顺序上有小坑,当我们从前往后遍历时,假如我更新了dp[1],然后想要更新dp[2]时,结果dp[2]与上一层的dp[1]有关,而dp[1]已经被更新了,那么得出的dp[2]就是不准确的.所以只能从后往前遍历。
遍历顺序其实也可以通过递推公式看出来,dp[j] = max(dp[j], dp[j - weight[i]] + value[i]),想求一个值,我需要知道它的上一层左边的值,所以前提是它上一层左边的值不发生变化。
所以只能从后往前遍历。
for(int i=0;i<=weight.size();i++)
{
for(int j=bagweight;j>=weight[i];j--)
{
}
}
5.举例推导dp数组。
总代码如下:
void test_1_wei_bag_problem() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagWeight = 4;
// 初始化
vector<int> dp(bagWeight + 1, 0);
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout << dp[bagWeight] << endl;
}
int main() {
test_1_wei_bag_problem();
}
416. 分割等和子集
题目链接:416. 分割等和子集 - 力扣(LeetCode)
文章链接:代码随想录 (programmercarl.com)
视频链接:动态规划之背包问题,这个包能装满吗?| LeetCode:416.分割等和子集
这道题其实就是背包问题。
分成两个元素和相等的子集,那么我们就可以先将所有元素相加再除以二就可以得到每个子集的元素和target。
与上面的背包问题不同的是,这里只有一个数组,没有区分体积和价值。
难就难在这一点,也就是对dp数组的定义不好想。
我们需要假设,物品0-3,他们的体积和价值都是那个元素值。就比如物品0的体积和价值都是1,物品1的体积和价值都是5.这样就转变成了背包问题了。
只要最后dp[target]=target,就代表可以分割。
代码如下:
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=0;
for(int i=0;i<nums.size();i++)
{
sum+=nums[i];
}
if(sum%2==1) return false;
int target=sum/2;
vector<int> dp(nums.size()*100,0);
for(int i=0;i<nums.size();i++)
{
for(int j=target;j>=nums[i];j--)
{
dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
if(dp[target]==target) return true;
return false;
}
};
Day41打卡成功,初识背包问题,有点小蒙圈。