第九章 动态规划 part05
1.LeetCode. 最后一块石头的重量 II
1.1题目链接:1049.最后一块石头的重量II
文章讲解:代码随想录
视频讲解:B站卡哥视频
1.2思路:本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了
1.3附加代码如下所示:
//题意理解就是把石头分为两堆重量最接近的然后这两者的差值就是最优解
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);//初始化dp数组
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];//这里sum-dp[target]式较大一堆的石头,然后减去另一堆较小的石头得到的就是最优解
}
};
2.LeetCode. 目标和
2.1题目链接:494.目标和
文章讲解:代码随想录
视频讲解:B站卡哥视频
2.2思路:采用回溯算法也可以,但是可能会超时;如何转化为01背包问题呢。
假设加法的总和为x,那么减法对应的总和就是sum - x。
所以我们要求的是 x - (sum - x) = target
x = (target + sum) / 2
此时问题就转化为,装满容量为x的背包,有几种方法。
2.3附加代码如下所示:
回溯算法:
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
if (sum == target) {
result.push_back(path);
}
// 如果 sum + candidates[i] > target 就终止遍历
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i + 1);
sum -= candidates[i];
path.pop_back();
}
}
public:
int findTargetSumWays(vector<int>& nums, int S) {
int sum = 0;
for (int i = 0; i < nums.size(); i++) sum += nums[i];
if (S > sum) return 0; // 此时没有方案
if ((S + sum) % 2) return 0; // 此时没有方案,两个int相加的时候要各位小心数值溢出的问题
int bagSize = (S + sum) / 2; // 转变为组合总和问题,bagsize就是要求的和
// 以下为回溯法代码
result.clear();
path.clear();
sort(nums.begin(), nums.end()); // 需要排序
backtracking(nums, bagSize, 0, 0);
return result.size();
}
};
//根据题意可以采用回溯算,但是可能会超时;换个思路采用dp算法,将数组分为加法组和减法组,
//题目要求的就是加法组中的元素和减法组中的元素之和等于目标值,看看有多少种划分加法组和减法组的方法
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int add;//划分为加法组和减法组,则可以得到,add+sub=sum且add-sub=target,进而得到add=(sum+target)/2
int sum=0;
for(int i=0;i<nums.size();i++)
{
sum+=nums[i];
}
if(abs(target)>sum)return 0;//如果target绝对值大于元素总和说明不存在满足的情况
//在这里如果(sum+target)得到的值为奇数的话说明不存在满足target的情况,因为不管是加法组还是减法组都是整数组成的,不存在非整数情况直接返回0
if((sum+target)%2==1)return 0;
add=(sum+target)/2;
vector<int>dp(add+1,0);//这里的dp数组表示的是装满j的背包有多少种装法
//这里dp[0]初始化为1,是因为这是划分的方式有多少种,如果数组[0] ,target = 0,那么 bagSize = (target + sum) / 2 = 0。 dp[0]也应该是1, 也就是说给数组里的元素 0 前面无论放加法还是减法,都是 1 种方法。
dp[0]=1;
for(int i=0;i<nums.size();i++)
{
for(int j=add;j>=nums[i];j--)
{
dp[j]+=dp[j-nums[i]];
}
}
return dp[add];
}
};
3.LeetCode.一和零
3.1题目链接:474.一和零
文章讲解:代码随想录
视频讲解:B站卡哥视频
3.2思路:本题中strs 数组里的元素就是物品,每个物品都是一个!
而m 和 n相当于是一个背包,两个维度的背包。
理解成多重背包的同学主要是把m和n混淆为物品了,感觉这是不同数量的物品,所以以为是多重背包。
但本题其实是01背包问题!
只不过这个背包有两个维度,一个是m 一个是n,而不同长度的字符串就是不同大小的待装物品。
dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
3.3附加代码如下所示:
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
vector<vector<int>>dp(m+1,vector<int>(n+1,0));//该dp[i][j]数组的意思是最大有i个0和最大有j个1的物品数量
dp[0][0]=0;
for(string str:strs)// 遍历物品
{
int x=0,y=0;
for(char c:str)//计算每一个元素所包含的0、1的个数
{
if(c=='0')x++;
else 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);//这里物品的价值就是物品种类数就是1
}
}
}
return dp[m][n];
}
};