力扣 1049 最后一块石头的重量 II
这个问题相当于把这堆石头划分成两堆 A
和 B
,两堆重量分别是 weightA
和 weightB
,那么最后剩下的石头就是:abs(sumA - sumB)
。而总重量是固定的(假设为 weight
),那么你找一个子集 A,使得它的重量尽可能接近 weight / 2
,另外一个子集就是剩下的石头。所以又变成了背包问题:
- 背包容量:
target = weight / 2
- 每个石头就是一个“物品”
- 目标是:在不超过容量
target
的前提下,选出一组石头,使得其总重量最大(尽可能接近target
)
代码如下:
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
if(stones.size() == 1) return stones[0];
int weight = 0;
for(auto& s : stones) weight += s;
int target = weight / 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 weight - (dp[target] * 2);
}
};
力扣 494 目标和
原问题可以转换为一个子集差值的问题,一个子集添加正号,一个子集添加负号,就有:
p o s − n e g = t a r g e t p o s + n e g = s u m pos - neg = target \\ pos + neg = sum pos−neg=targetpos+neg=sum
因此问题变成了从数组中选出一些数,使它们的和等于 ( t a r g e t − s u m ) / 2 (target - sum) / 2 (target−sum)/2,问有多少种选法。
dp数组定义
dp[j]
表示:和为j
的子集个数。初始化dp[0] = 1
,只有一种情况和为0,即什么都不选择。最终本题的目的是求dp[bagSize]
。
状态转移公式
如果之前已经有若干种方法可以组成一个和为 j − n u m j - num j−num 的组合,那么现在我们只要把当前数字加进去,就能使原来的和 j − n u m j - num j−num 变成 j j j。因此,本题的状态转移公式为:
dp[j] += dp[j - num]
dp[j - num]
表示「不使用当前数字时」有多少种方法可以凑成(j - num)
。- 加入当前数字
num
后,这些方法就可以凑成j
,所以累加到dp[j]
上。
代码实现
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = accumulate(nums.begin(), nums.end(), 0);
// (target + sum) 必须是偶数才能除以 2
if ((sum - target) % 2 != 0 || sum < abs(target)) return 0;
int bagSize = (sum - target) / 2;
vector<int> dp(bagSize + 1, 0);
dp[0] = 1; // 组成 0 的方法只有一种
for (int num : nums) {
for (int j = bagSize; j >= num; j--) {
dp[j] += dp[j - num]; // 统计方案数
}
}
return dp[bagSize];
}
};
力扣 474 一和零
实际上还是一个0-1背包问题,每个字符串可以选择放入子集或者不放。这里要判断的有两个维度,所以定义一个二维数组来进行动态规划。
- 确定dp数组
p[i][j]
:最多有i
个0
和j
个1
的strs
的最大子集的大小。 - 状态转移公式:这里同样的,单个字符串可以选择放和不放,那么取这二者选择的最大值即可:
dp[i][j] = max(dp[i][j], dp[i - zero][j - one] + 1)
。
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for(auto& str : strs){
int one = 0, zero = 0;
for (auto& c : str) {
if (c == '0') zero++;
else one++;
}
for(int i = m; i >= zero; i--){
for(int j = n; j >= one; j--){
dp[i][j] = max(dp[i][j], dp[i - zero][j - one] + 1);
}
}
}
return dp[m][n];
}
};
写在最后的碎碎念:今天的内容偷了点懒,写的都是最优的动态规划解法,前两题使用的是滚动数组,第三题鉴于有两个维度的限制,因而使用二维数组。没ac成功,但捋顺了思路之后,都还是蛮好理解的,再接再厉。