1049. 最后一块石头的重量 II
本题就和 昨天的 416. 分割等和子集 很像了,可以尝试先自己思考做一做。
视频讲解:动态规划之背包问题,这个背包最多能装多少?LeetCode:1049.最后一块石头的重量II_哔哩哔哩_bilibili
这道题需要间接考虑,可以转化为能不能将整个数组尽量分成两个和一样的分数组,这样求最后的石头的质量就是求两个分数组的和之间的最小差值,就变成了和。
416. 分割等和子集 (opens new window)差不多一个思路。还要注意一点是,无论这两道题哪一题都不能通过sum是偶数来直接得出结果,只是有这种恰好分成两个总和相等的分数组的可能性而已(其实只是前提条件,并不是等价),也并不一定如此。
int lastStoneWeightII(vector<int>& stones) {
int sum=0,half=0;
for(int i=0;i<stones.size();i++) sum+=stones[i];
// if(sum%2==0) return 0;
half=sum/2;
vector<int> dp(half+1,0);
for(int i=0;i<stones.size();i++){
for(int j=half;j>=stones[i];j--){
dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);
}
}
return sum-dp[half]*2;
}
494. 目标和
大家重点理解 递推公式:dp[j] += dp[j - nums[i]],这个公式后面的提问 我们还会用到。
感觉这道题真的好难懂啊!!!首先把它能套到动态规划就不太容易想到,文章中说其实对于每个数值选择运算符号,因为只有+、-号所以大可以将数组直接划分成两个分数组,plus数组和subtract数组,前者总和加上后者总和=sum,而前者总和减去后者总和=target,所以就可以推导出plus数组的总和=(sum+target)/2,那么这个数就相当于背包的容量。
首先还是dp数组的含义,dp[j] 代表总和为j 时,plus分数组的设置能有几种可行方法(一旦确定一个分数组,那么另一个也就定下来了,是剩下的元素),那么dp[j]+=dp[ j-nums[i] ] (后续涉及到方法数的大多都这个递归方式),也就是+确定加入nums[i]之后(得到减去后对应的“容量”)能有几种方法*1,因为是先遍历物品后遍历背包,所以不会有重复的地方。还有一点,初始化dp数组的时候,必须将dp[0]设置为1,虽然总和为0的方法数不一定是1(例如集合[0, 0 ,0]),即使初始化为1,最后也可能会增加,但如果初始化为0,dp数组就永远都是0(因为遍历背包是倒序遍历,而且除0外所有都初始化为0,所以如果该容量存在方法数,那么一定是从dp[0]开始加起来的,dp[0]=0,那加来加去都只能是0)。
值得一提的是首先要判断是否存在可行方案,如果得到的size不是整数(因为题目规定必须都是整数,所以加和不可能不是整数)(也就是看(target+sum)/2得到的背包容量是否是个整数,如果是个整数,那就意味着可能有解)或者干脆target大于sum的话,那就肯定没有解决方案了。
int findTargetSumWays(vector<int>& nums, int S) {
int sum = 0;
for (int i = 0; i < nums.size(); i++) sum += nums[i];
if (abs(S) > sum) return 0; // 此时没有方案
if ((S + sum) % 2 == 1) return 0; // 此时没有方案
int bagSize = (S + sum) / 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];
}
474.一和零
通过这道题目,大家先粗略了解, 01背包,完全背包,多重背包的区别,不过不用细扣,因为后面 对于 完全背包,多重背包 还有单独讲解。
视频讲解:动态规划之背包问题,装满这个背包最多用多少个物品?| LeetCode:474.一和零_哔哩哔哩_bilibili
首先明确这不是多重背包,而是01背包,每个字符串只能选取1次,但是是有两个维度的,所以必须使用二维数组来作为dp数组。
- 确定dp数组(dp table)以及下标的含义。dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
- 确定递推公式。dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1),和经典01背包的基本一致。
- dp数组初始化。首先dp[0][0]=0,这是肯定的,而其他的值也必须为0,否则取max没意义。
- 遍历顺序。这道题虽然是使用了二维数组,但是遍历顺序是和01背包使用一维数组作为dp数组时的遍历顺序是一致的,二维只是因为有两个维度而已,至于内部的两个循环(也就是哪个维度更先遍历的问题),顺序其实无所谓,两个维度是平等的。
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];
}