1 1049. 最后一块石头的重量 II
看了题解,理解一会儿,才知道为什么这题要转换成416. 分割等和子集很像AC代码:
cpp
class Solution {
public:
/* 一开始看到这题和416. 分割等和子集很像 真的不理解
但是懒得严谨证明两堆石头重量差值最小就是目标对撞的两堆石头
感觉是对的
7,4 1,1,2,8
2,7,1,1 4,8
*/
int dp[1501]; // 容积的i的背包最多能装的物体的重量之和
int lastStoneWeightII(vector<int>& stones) {
dp[0] = 0;
int total = 0;
for(auto t : stones)total+=t;
int sum = total;
total /= 2;
cout << sum << " " << total << endl;
for(int i = 0; i < stones.size();i++)
{
for(int j = total; j >= 0; j--)
{
if(j < stones[i])dp[j] = dp[j];
else dp[j] = max(dp[j],dp[j - stones[i]] + stones[i]);
}
for(int j = 0; j <= total; j++)cout << dp[j] << ' ';
puts("");
}
return sum - dp[total] - dp[total];
}
};
2 494. 目标和
" 这次和之前遇到的背包问题不一样了,之前都是求容量为j的背包,最多能装多少。本题则是装满有几种方法。其实这就是一个组合问题了。"
一开始的dp[]数组含义写错了,后来学习题解和视频,才学习到dp[]数组的含义,然后发现这个转移方程比较难理解,和之前学过的不大一样。**大概理解了88%,下次再做一次看懂了没有。**详细解释见注释,AC代码:
求组合类问题 的公式,在求装满背包有几种方法的情况下,递推公式一般为:
dp[j] += dp[j - nums[i]]
这个公式在后面在讲解背包解决排列组合问题的时候还会用到!
cpp
class Solution {
public:
/*
物体:nums个数,01表示每个整数前面添加+还是负
价值:nums[i] 重量:+- nums[i]
*/
int dp[2010]; // 容量是 j 的背包 装满时候不同的方式
/*
我一开始的写法:
"容积为 i 的背包能够正好装nums个数且重量正好是i的不同组合的最大值"
但这样的问题是没有转换位01背包 即这些物体是可以分成2堆的
本题的思路:
设一个left集合装nums中所有+数,right装nums中所有-数
left.sum-right.sum = target
left.sum+righr.sum = sum
所以,left.sum = ( target + sum ) / 2
一个背包容量是( target + sum ) / 2的背包设只装正数
求装满的不同方式有多少种
*/
/*
状态转移:(我直接抄的,和之前做过的都不太一样)
dp[j] += dp[j - nums[i]]
初始化:
dp[0] = 1(凑的 没有实际含义)
顺序:
for(int i = 0; i < nums.size();i++)
{
for(int j = ( target + sum ) / 2; j >= 0; j--)
{
}
}
模拟:
1 1 0 0 0
1 2 1 0 0
1 3 3 1 0
1 4 6 4 1
1 5 10 10 5
*/
int findTargetSumWays(vector<int>& nums, int target)
{
dp[0] = 1;
int sum = 0;
for(auto t : nums)sum += t;
if((target+sum) % 2 != 0)return 0;
for(int i = 0; i < nums.size();i++)
{
for(int j = ( target + sum ) / 2; j >= 0; j--)
{
if(j >= nums[i])dp[j] += dp[j - nums[i]];
}
for(int j = 0; j <= ( target + sum ) / 2; j++)
cout << dp[j] << ' ';
puts("");
}
return dp[( target + sum ) / 2];
}
};
3 474. 一和零
本题两个地方卡住,看了这两个地方的题解tips,独立写完,AC代码:
- 一个字符串数组中分成2堆,一堆中有n个1,有m个0
一个m一个n怎么在一个背包中体现?
【【看了提示1】】题解tips:用两个背包====》二维dp背包
2. 遍历m*n的二维数组时候// for(int i = 0; i <= n; i++) 错误!!!! 【【看了提示2】】
for(int i = n; i >= 0; i--)
i-- 而不能是 i++ 的 原因是:dp[i][j] = max(dp[i-a][j-b] + 1 , dp[i][j]); 这里的 i-a 和 j-b 暗含了对于当前 i,j,左上方的这些数都要是k-1次的即上一次的。
cpp
class Solution {
public:
/*
转换为01背包
一个字符串数组中分成2堆,一堆中有n个1,有m个0
一个m一个n怎么在一个背包中体现?
【【看了提示1】】题解tips:用两个背包====》二维背包
dp[n][m] (i,j)能装i个1和能装j个0的背包装满时候 的最大物体个数
*/
int dp[110][110]; //(i,j)能装i个1和能装j个0的背包装满时候的最大物体个数
/*
转换方程:
设当前k物体有a个1和b个0
dp[i][j] = max(dp[i-a,j-b]+1,dp[i,j])
初始化
dp[0][0] = 0 其他都是0
顺序:
for(int k = 0; k < str.size();k++)
{
for(int i = 0; i < n; i++) 错误!应该i--
{
for(int j = 0; j < m; j++)错误!应该j--
{
}
}
}
模拟:
------
*/
int findMaxForm(vector<string>& strs, int m, int n)
{
dp[0][0] = 0;
for(int k = 0; k < strs.size();k++)
{
int a = 0;
int b = 0;
for(int i = 0; i < strs[k].size(); i++)
{
if(strs[k][i] == '1')a++;
else b++;
}
// for(int i = 0; i <= n; i++) 【【看了提示2】】
for(int i = n; i >= 0; i--)
{
// for(int j = 0; j <= m; j++)
for(int j = m; j >= 0; j--)
{
if(i >= a && j >= b)dp[i][j] = max(dp[i-a][j-b] + 1 , dp[i][j]);
else dp[i][j] = dp[i][j];
}
}
// for(int i = 0; i <= n; i++)
// {
// for(int j = 0; j <= m; j++)
// {
// cout << dp[i][j] << ' ';
// }
// cout << endl;
// }
// cout << endl;
}
return dp[n][m];
}
};
总结:01背包总结
总结前几个刷过的01背包的应用:
纯01背包:求给定背包容量,装满一个背包,里面能装物品的最大价值总和是多少。
416. 分割等和子集:求给定背包容量,能否装满一个背包
1049. 最后一块石头的重量 II:求给定背包容量,在不超限情况下,背包能够装的最大的重量。
494. 目标和:求给定背包容量,装满背包有多少种方式。
474. 一和零:求给定背包容量,装满背包最多有多少个物品。