自己没想出来呜呜。我思考的时候以为是有特定顺序才行,然后回想一下背包问题好像不能解决顺序的,只能解决组合的,也就是每个东西有或者没有,然后我就觉得我想不出来了。这个时候我应该联想到背包问题只能解决组合的,所以这道题我那样想顺序的想法就是错的,要转变思路
看了随想录学会了,自己写了:
先用数学想一下 n 个 (y-x)相加,即这些y之和 - 这些x之和 =》分成重量尽可能接近的两堆 =》这题就和416. 分割等和子集 变成几乎一样的了。两堆石头数量不一样也没关系,关键是重量和
注意一维的初始化就包含在那两层for loop里面 i = 0的时候了。
另外dp[target] 代表的是 在target为上限的情况下,能达到的最大重量和(接近sum/2的,所以也是小半,所以最后return是被减的)
int lastStoneWeightII(vector& stones) {
int sum=accumulate(stones.begin(),stones.end(),0);
int target = sum / 2;
vector dp(target+1,0);
for (int i = 0; i < stones.size(); i++) { // 遍历物品
for (int j = target; j >= stones[i]; j--) { // 遍历背包
dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
}
}
return (sum - dp[target]) - dp[target];
}
又用二维写了一遍,注意二维不适合把初始化也放到那个两层for loop中,因为二维会有i-1的写法,需要特殊处理,一处理就和单独初始化没差别了。这是我写的二维
int lastStoneWeightII(vector& stones) {
int sum=accumulate(stones.begin(),stones.end(),0);
int target = sum / 2;
vector> dp(stones.size(),vector(target+1,0));
//initialize
for(int j=stones[0];j<=target;j++) dp[0][j]=stones[0];
for (int i = 1; i < stones.size(); i++) {
for (int j = 1; j <=target; j++) {
if(j>=stones[i]){
dp[i][j]=max(dp[i-1][j],dp[i-1][j-stones[i]]+stones[i]);
}
else dp[i][j] = dp[i - 1][j];
}
}
return (sum - dp[stones.size()-1][target]) - dp[stones.size()-1][target];
}
这是gpt写的,把初始化放到一起的:
int lastStoneWeightII(vector& stones) {
int sum=accumulate(stones.begin(),stones.end(),0);
int target = sum / 2;
vector> dp(stones.size(),vector(target+1,0));
for (int i = 0; i < stones.size(); i++) {
for (int j = 0; j <=target; j++) {
if (i == 0){
dp[i][j] = (j >= stones[i]) ? stones[i] : 0;
}else{
if(j >= stones[i]){
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - stones[i]] + stones[i]);
}else{
dp[i][j] = dp[i - 1][j];
}
}
}
}
return sum - 2 * dp[stones.size()-1][target];
}
审题注意看下面Constraints target可能为负数
这一步我想出来了,然后确定了是背包变形,然后我的思路就歪了。其实我也没太看懂gpt说的我哪里错了,但是我自己试了20min写实现,我的思路确实不对。
这题回溯思路是没错,但是会超时
现在来看随想录的背包思路:
2x=target+sum 所以右边这俩为奇数无解。或者target abs>sum 也无解。
这次和之前遇到的背包问题不一样了,之前都是求容量为j的背包,最多能装多少。本题则是装满有几种方法。其实这就是一个组合问题了。
1.确定dp数组和下标的含义: dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
其实也可以使用二维dp数组来求解本题,dp[i][j]:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。
2.递推公式:假设可供选择的是 1 2 3 4 5(现在没有负数了) dp[5](构成答案是5有几种情况)= 选择了1+ dp[5-1] 或 选择了 2 +dp [5-2] 。。。就是每个可供选的都选一次的情况列出来
所以dp[j]+=dp[j-nums[i]] 随想录说这个公式在后面在讲解背包解决排列组合问题时还会用到!
3.初始化:看他写的有点晕,但基本理解了dp[0]=1
4. 确定遍历顺序:就正常顺序 5. 略
自己写了一遍代码:
int findTargetSumWays(vector& nums, int target) {
int sum=accumulate(nums.begin(),nums.end(),0);
if ((target + sum) % 2 == 1 || abs(target) > sum) return 0;
int bagSize = (target + sum) / 2;
vector dp(bagSize + 1, 0);
dp[0] = 1;//important
for (int i = 0; i < nums.size(); i++) {
for (int j = bagSize; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[bagSize];
}
没想出来,以为要用3d的dp表,还以为dp里面要存set,都不对。算是自己没学过的特殊的解法,学学看吧。随想录思路如下:
就因为这背包的两个维度,本题必须是2d的dp 。i j 分别是 0的个数,1的个数。就像两个一维的背包解法套在一起了。所以遍历也要按1d背包的从后往前。for 物品 for i for j ,三个for loop套在一起。其实我想的3d dp表也算是对的,但是我们要把2d降成1d就变成正确的思路了。weight的限制(背包容量)是两个维度
int findMaxForm(vector& strs, int m, int n) {
vector> dp(m + 1, vector (n + 1, 0));
for (string &str : strs) {
int oneNum = 0, zeroNum = 0;
for (char &c : str) {//相当于把str的两个重量维度算出来
if (c == '0') zeroNum++;
else oneNum++;
}
for (int i = m; i >= zeroNum; i--) {//对比标准是>=weight【0】
for (int j = n; j >= oneNum; j--) {
dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
}
}
}
return dp[m][n];
}
自我总结:
● 1049. 最后一块石头的重量 II:转化成把石头尽可能分成重量接近sum/2两堆,推导公式就是普通的
● 494. 目标和:也是转化 sum1=(sum+target)/2 ,然后是求和为sum1有多少种组合,dp[j]放组合数,dp[0]=1, dp[j]+=dp[j-nums[i]]
● 474.一和零: 特殊在重量和背包容量,有两个维度衡量。两个1d套在一起记得都是倒序遍历,总共三个for loop,最外是物品