参考
代码随想录
题目一:LeetCode 1049. 最后一块石头的重量 II
这个题和之前的“416 分割等和子集”非常的类似,如果给出的stones[]数组能被分成相等的两个数组,那么剩余石头的最小重量就是0。求解思路和分割等和子集一样,只是最后的返回值不同。套用01背包,物品的重量和价值都是stones[]。
- 确定dp数组及其下标的含义
dp[j] :背包最大承载重量j得到的最大价值为dp[j],对应本题,最大总和j对应的实际能达到的最大总和为dp[j]。 - 确定递推关系
一维dp数组01背包的递推公式如下:
dp[j] = max(dp[j],dp[j-stones[i]]+stones[i])
- dp数组初始化
题目中给出1 <= stones[i] <= 100,因此dp数组全部初始化为0。 - 确定遍历顺序
遍历顺序在一维dp数组01背包中已经分析过了,如下:
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]);
}
}
- 举例推导dp数组
以stones = [2,4,1,1]为例,推导得到的dp数组如下:
整体代码实现如下:
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int sum = 0;
for(int i = 0; i < stones.size(); i++)
sum += stones[i];
int target = sum / 2;
vector<int> 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] * 2;
}
};
题目二:LeetCode 494. 目标和
假设添加“+”或者“-”之后,加法总和为x,则减法总和为sum-x(注意这里指的是添加减号的那些数的总和),其中sum是添加符号之前的数组总和,则有
target = x + (-(sum-x)) = 2x-sum
因此可以得到x的表达式:
x = (sum + target) / 2
因此将问题转化为数组中有多少种和为x的组合,套用01背包,就是装满容量为x的背包,有多少种方法,此时的背包容量bagSize就是这里的x。
因为给定的元素都为整数,所以如果sum+target为奇数的话是不可能得到和为x的组合的,因为奇数除以2之后结果会被截断。
- 明确dp数组及其下标的含义
dp[j]:装满容量为j的背包有dp[j]种方法 - 确定递推公式
对于数组中的某一个元素nums[i],得到dp[j]可以有dp[j - nums[i]]种方法,而nums[i]可以是数组中满足条件的任一个数,因此可以得到递推公式:
dp[j] += dp[j - nums[i]]
- 初始化dp数组
dp[0] = 1,其他初始化为0 - 确定遍历顺序
nums放在外循环,target在内循环,且内循环倒序。 - 举例推导递推公式
nums = [1,1,1,1,1],target = 3
完整的代码实现如下:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = 0;
for(int i = 0; i < nums.size(); i++)
sum += nums[i];
if((sum + target) % 2 == 1) return 0;
if(sum < abs(target)) return 0;
int bagSize = (sum + target) / 2;
vector<int> dp(bagSize+1,0);
dp[0] = 1;
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];
}
};
题目三:LeetCode 474.一和零
本题中strs 数组里的元素就是物品,每个物品都是一个,而m 和 n相当于是一个背包,两个维度的背包。
- 确定dp数组及其下标的含义
dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。 - 确定递推公式
dp[i][j] = max(dp[i][j],dp[i-zeroNum][j-oneNum]+1)
- dp数组初始化
因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。 - 确定遍历顺序
01背包一定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历!
for (string str : strs) { // 遍历物品
int oneNum = 0, zeroNum = 0;
for (char c : str) {
if (c == '0') zeroNum++;
else oneNum++;
}
for (int i = m; i >= zeroNum; i--) { // 遍历背包容量且从后向前遍历!
for (int j = n; j >= oneNum; j--) {
dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
}
}
}
- 举例推导dp数组
strs = [“10”,“0001”,“111001”,“1”,“0”],m = 3,n = 3
完整的代码实现如下:
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
vector<vector<int>> dp(m + 1, vector<int> (n + 1, 0)); // 默认初始化0
for (string str : strs) { // 遍历物品
int oneNum = 0, zeroNum = 0;
for (char c : str) {
if (c == '0') zeroNum++;
else oneNum++;
}
for (int i = m; i >= zeroNum; i--) { // 遍历背包容量且从后向前遍历!
for (int j = n; j >= oneNum; j--) {
dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
}
}
}
return dp[m][n];
}
};