上一篇算法整理——【动态规划练习(2)01背包】-CSDN博客我们学习了01背包,现在我们继续练习01背包的问题。
一、最后一块石头的重量II
题目为1049. 最后一块石头的重量 II - 力扣(LeetCode),有一堆石头,用整数数组 stones
表示。其中 stones[i]
表示第 i
块石头的重量。每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x
和 y
,且 x <= y
。那么粉碎的可能结果如下:如果 x == y
,那么两块石头都会被完全粉碎;如果 x != y
,那么重量为 x
的石头将会完全粉碎,而重量为 y
的石头新重量为 y-x
。最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0
。
本题的重点思路就是理解题目后发现本体其实就是需要让石头被分成重量相同的两堆,这样才能让撞完后的重量最小,这样就变成上篇博客中的分割等和子集相似的01背包问题了。(建议先看上一篇博客的分割等和子集问题的分析,以下分析过程与该题基本相同所以只会简要说明)
利用动规五部曲解决问题:
①数组和下标的含义。我们继续用一维动规数组来表示,dp[j]表示容量为j的包最多可以背的价值(本题中价值=重量)。
②确定递推公式。dp[j] = max(dp[j], dp[j-stones[i]]+stones[i]);
③初始化。可以计算出所有石头的总重量然后除以2+1作为dp数组大小,或者直接用题目给出的数据范围开到较大的例如本题为15001,初始化的值为0。
④遍历顺序。一维数组应该把物品遍历放在外层,背包容量遍历放在内层且倒序遍历(具体原因见上一篇博客)。
⑤得到dp数组并检查。
完整代码如下:
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int sum = 0;
for(int i = 0; i<stones.size(); i++)
{
sum+=stones[i];
}
vector<int> dp(sum/2+1,0);
for(int i = 0; i<stones.size(); i++)
{
for(int j = sum/2; j>=stones[i]; j--)
{
dp[j] = max(dp[j],dp[j-stones[i]]+stones[i]);
}
}
return abs(dp[sum/2]-sum+dp[sum/2]);
}
};
二、目标和
题目为494. 目标和 - 力扣(LeetCode),给你一个非负整数数组 nums
和一个整数 target
。向数组中的每个整数前添加 '+'
或 '-'
,然后串联起所有整数,可以构造一个表达式。返回可以通过上述方法构造的、运算结果等于 target
的不同表达式的数目。
首先分析问题,给我的第一反应是好像可以使用回溯算法给暴力算出来,但会超时。我们可以把这个题的+和-理解为需要找到一组数的和-一组数的和=target。且我们指导所有的数的和,所以式子为一组数的和-(sum-刚才那组数的和)= target,所以这组数的和为 (target + sum)/2 。
然后我们的目标就变成了找有多少种组合的和为 (target + sum)/2。此时可以转化为回溯法中的组合总和,现在我们用动态规划方法解决。
动态规划五部曲:
①确定dp数组以及下标的含义。我们采用一维数组,dp[j]表示填满j的容量有dp[j]种方法。
②递推公式。对于每个nums[i],我们的dp[j]就可以增加dp[j-nums[i]]种方法。
③初始化。dp[0]移动要初始化为1。
④遍历顺序。对于一维数组,物品(本题的nums)要放在外循环,背包要放在内循环并且倒叙。
⑤打印并检查dp数组。
完整代码如下:
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(abs(target)>sum) return 0;
if((target+sum)%2 == 1) return 0;
vector<int> dp((target+sum)/2+1,0);
dp[0] = 1;
for(int i = 0; i<nums.size(); i++)
{
for(int j = (target+sum)/2; j>=nums[i]; j--)
{
dp[j] += dp[j-nums[i]];
}
}
return dp[(target+sum)/2];
}
};
三、一和零
题目为474. 一和零 - 力扣(LeetCode),给你一个二进制字符串数组 strs
和两个整数 m
和 n
。
请你找出并返回 strs
的最大子集的长度,该子集中最多有 m
个 0
和 n
个 1
。如果 x
的所有元素也是 y
的元素,集合 x
是集合 y
的子集。
本题的背包有m、n两个维度,本质上还是01背包问题。
使用动规五部曲:
①确定dp数组及其下标含义。dp[i][j]为最多有i个1和j个0的最大子集大小。
②递推公式。如果当前字符串有p个1有q个0,则dp[i][j] = dp[i-p][j-q]+1。
③初始化。初始化为0。
④遍历顺序。对于每一个维度来说是个1维的dp数组,所以外层遍历物品,内层便利背包且从后往前。
⑤检查dp数组。
完整代码如下:
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
vector<vector<int>> dp(m+1,vector<int>(n+1,0));
for (string str : strs)
{
int p = 0, q = 0;
for (char c : str)
{
if (c == '0') q++;
else p++;
}
for (int i = m; i >= q; i--)
{
for (int j = n; j >= p; j--) {
dp[i][j] = max(dp[i][j], dp[i - q][j - p] + 1);
}
}
}
return dp[m][n];
}
};
说明:本文为作者整理知识点用于复习巩固,参考了代码随想录的讲解,有问题可以联系作者欢迎讨论~