最后一块石头的重量 :给定背包容量,尽可能装,最多能装多少
题目链接:力扣
本题的实质是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。
本题物品的重量为stones[i],物品的价值也为stones[i]。
对应着01背包里的物品重量weight[i]和 物品价值value[i]。
- 确定dp数组以及下标的含义
dp[j]表示容量为j的背包,最多可以背最大重量为dp[j]。 - 确定递推公式
01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
本题则是:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]); - dp数组初始化
dp[j]中的j表示容量,那么最大容量(重量)是所有石头的重量和。
提示中给出1 <= stones.length <= 30,1 <= stones[i] <= 1000,所以最大重量就是30 * 1000
而我们要求的target其实只是最大重量的一半,所以dp数组开到15000大小就可以了.
dp[j]都初始化为0即可 - 遍历顺序
遍历背包的for循环放在内层,且内层for循环倒序遍历 - 举例推导dp数组
最后dp[target]里是容量为target的背包所能背的最大重量。
那么分成两堆石头,一堆石头的总重量是dp[target],另一堆就是sum - dp[target]。
在计算target的时候,target = sum / 2 因为是向下取整,所以sum - dp[target] 一定是大于等于dp[target]的。
那么相撞之后剩下的最小石头重量就是 (sum - dp[target]) - dp[target]。
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int target = 0;
int sum = 0;
for(int i=0;i<stones.size();i++)
{
sum += stones[i];
}
target = sum/2;
vector<int>dp(100*30/2+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];
}
};
目标和: 给定背包容量,装满背包有多少种方法。
题目链接:力扣
这题 乍一看和背包问题联系不起来,其实需要先进行一番推导,摘抄来自leetcode的评论。
原问题等同于: 找到nums一个正子集和一个负子集,使得总和等于target
我们假设P是正子集,N是负子集
例如: 假设nums = [1, 2, 3, 4, 5],target = 3,
一个可能的解决方案是+1-2+3-4+5 = 3 这里正子集P = [1, 3, 5]和负子集N = [2, 4]
那么如何将其转换为子集求和问题:
sum(P) - sum(N) = target
sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)
2 * sum(P) = target + sum(nums)
因此,原来的问题已转化为一个求子集的和问题:
找到nums的一个子集 P,使得sum(P) = (target + sum(nums)) / 2
上面的公式已经证明target + sum(nums)必须是偶数,否则输出为0
- 确定dp数组以及下标的含义
dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法 - 确定递推公式
只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法。
例如:dp[j],j 为5
已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 容量为5的背包
已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 容量为5的背包
已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 容量为5的背包
那么凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来。
所以求组合类问题的公式,都是类似这种:dp[j] += dp[j - nums[i]] - dp数组如何初始化
初始化 dp[0] 为 1。 - 确定遍历顺序
nums放在外循环,target在内循环,且内循环倒序。 - 举例推导dp数组
int findTargetSumWays(vector<int>& nums, int target)
{
int sum = accumulate(nums.begin(), nums.end(), 0);
int aim = target + sum;
int count = 0;
if(aim % 2) return count;
if(abs(target) >sum) return 0;
aim = aim/2;
vector<int>dp(aim+1,0);
dp[0]=1;
for(int i=0; i< nums.size();i++)
for(int j=aim;j>=nums[i];j--)
{
dp[j] += dp[j-nums[i]];
}
return dp[aim];
}
一和零:给定背包容量,装满背包最多有多少个物品。
题目链接:力扣
看到有两个维度,一开始以为是多重背包,进入了误区
其实多重背包是每个物品数量不同的情况。
这道题本质上还是01背包问题,因为每一个字符串只取一次,而背包是两个维度的,即m和n
- 确定dp数组(dp table)以及下标的含义
dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。 - 确定递推公式
01背包的模板公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
设 strs里遍历到的字符串有zeroNum个0,oneNum个1。
字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i]),也就是1
所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1); - dp数组如何初始化
因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。 - 确定遍历顺序
这里 物品就是strs里的字符串,背包容量就是题目描述中的m和n。
所以先正序遍历strs,再分别倒叙遍历m和n - 举例推导dp数组
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
vector<vector<int>> dp(m+1,vector(n+1,0)); //二维背包
for(int i=0; i<strs.size(); i++)
{
int Num1=0;
int Num0=0;
for(int j=0;j<strs[i].size();j++)
{
if(strs[i][j] == '1')
Num1++;
else
Num0++;
}
for(int a=m; a>=Num0;a--)
for(int b=n; b>=Num1; b--)
{
dp[a][b] = max(dp[a][b],dp[a-Num0][b-Num1]+1);
}
}
return dp[m][n];
}
};