LeetCode 1049. 最后一块石头的重量 II
题目链接:1049. 最后一块石头的重量 II
踩坑:最后的返回值应该是sum减去已经凑出来的石头堆两次。
思路:关键在于认识到不止两个石头的差可以得到,两堆石头的差也能得到。所以题目可以变成将这一堆石头分成两堆重量相似的石头。求解过程就变成了分割等和子集
代码:
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int sum = accumulate(stones.begin(), stones.end(), 0);
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]) - dp[target];
}
};
LeetCode 494. 目标和
题目链接:494. 目标和
踩坑:装满背包的总方法数那里绕了点
思路:核心是将所有元素分成正数和负数两组,正数+负数=target,正数-负数=sum,以此可以求出正数的总和,也即背包的大小。
- 动态数组的含义:dp[j]:装满大小为 j 的背包一共有多少种方法
- 递推公式:dp[j] = dp[j] + dp[j - weight[i]]。注意,等号左边的dp[j]是新的,右边的dp[j]是旧的,表示的是如果不放入当前物品装满 j 的背包有多少种方法,dp[j - weight[i]]表示放入当前物品将 j 的背包装满有多少种方法。
- 初始化动态数组:dp[0] = 1。当target为0,数组为{0}时,代入公式得正数=0,有解
- 遍历顺序:先物品再背包
代码:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if((sum + target) % 2) return 0;
int bagsize = (sum+target)/2;
if(bagsize < 0) return 0;
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] + dp[j-nums[i]];
}
}
return dp[bagsize];
}
};
LeetCode 474.一和零
题目链接:474.一和零
踩坑:对于有两个维度的物品的背包问题不熟悉。误解了题意,理解成满足最多m个0,n个1的子集有多少种了,应该是满足要求的最大子集。
思路:
- dp数组含义:dp[i][j]:最多i个0,j个1的子集的最大长度
- 递推公式:dp[i][j] = max(dp[i][j], dp[i - x][j - y]+1)
- 初始化:dp[0][0] = 0,其他也都是0
- 遍历顺序:物品正向,背包反向
代码:
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
dp[0][0] = 0;
for(string str: strs)
{
int x = 0, y = 0;
for(char s: str)
{
if(s == '0') x++;
if(s == '1') y++;
}
for(int i = m; i >= x; i--)
{
for(int j = n; j >= y; j--)
{
dp[i][j] = max(dp[i][j], dp[i-x][j-y]+1);
}
}
}
return dp[m][n];
}
};