第一次复习时间:09-27
第三次复习时间:很久没刷题了 2022-07-23
微信参考1
微信参考2
参考1
参考2
参考3
写给自己的废话
:相当于只是利用同一层当前位置的数据和左边的某一个数据
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
常见的背包我们就分成两种
外层循环num数组(物品)
,内层循环遍历target并且倒序
外层循环num数组(物品)
,内层循环遍历target并且正序+if判断 j>=nums[i](就是要求背包大于物品的大小)
。但是遇到组合问题中的排列即需要考虑元素间的顺序,此时要求target在外,nums数组在内颠倒
背包都有三种问题分类0927补充 :只是模板实际i要换成j来写
特别注意是排列还是组合
解题步骤
当前了需要注意的是有时候不会直接给你nums数组,或者不会直接给你target,你需要自己把抽象的问题具体化
最后看看别人的总结
补充:区别回溯和背包例如39:组合总和 如果需要有一个数组 一个target 需要返回所有组合的可能的方案 (不是方案书 不是存在 最值 那这个题就只能回溯 暴力所有的情况)
补充:看到数组 最值 是否 方案数 看看能不能转换为背包
补充:最值中的数量问题+1 价值问题+num[i]
补充: 特别注意最小值问题的初始化(完全背包的第一题)
补充:完全背包 内部正序 j>=coins[i] 记得等于 容易忘记
1、最值问题: dp[i] = max/min(dp[i], dp[i-nums]+1)或dp[i] = max/min(dp[i], dp[i-num]+nums);
2、存在问题(bool):dp[i]=dp[i]||dp[i-num];
3、组合问题:dp[i]+=dp[i-num];
-for(int j = bagWeight; j >= weight[i]; j--)注意在一维初始化是包含在了递归方程中如下示例代码
再遇到一个nums 一个 目标和的题目就要想到背包问题。从结果上理解,如果背包最大化是 放入 dp【10】2 3 5 那是不是也可以理解成dp【10】 取决于dp【5】 dp【5 】取决于dp【2】 有点像回溯的改进 。
class Solution {
public:
bool canPartition(vector<int> &nums)
{
int sum = accumulate(nums.begin(), nums.end(), 0);
if(sum % 2 ==1) return false;
int target =sum/2;
vector<int> dp(target+1,0);//下标表示背包和 最大=target
dp[0]=true; //初始化:target=0不需要选择任何元素,所以是可以实现的
for(int i =0;i<nums.size();i++)//遍历数组
{
for(int j =target;j>=nums[i];j--)//逆序遍历背包
{
//不选择物品i dp[i-1][j] 选择物品i dp[i-1][j-num[i]]
dp[j]=dp[j]||dp[j-nums[i]];//表示上一层
}
}
return dp[target];
}
};
第一次复习
第三遍复习:因为很久没做过了 很多都忘记了 补充一下对于地推公式的理解,就是你看二维的 在i个物品j的大小 取决于 i-1个商品的选择 + 第i个商品到底选择不选择(确定不选择呢 不选择那就是i-1 j的大小不变 。 如果选择 那就是 i-1 j-一个值 也合理)。简单的说就是把从i个商品中的选择 割裂成i-1的商品选择 和 确定第i个商品选择 和 不选择两种情况
写错的地方就是>=
class Solution {
public:
bool canPartition(vector<int>& nums) {
// Num 有了 target 就是目标的总数的一半 存在问题 而且不是排列
int sum = accumulate(nums.begin(),nums.end(),0);
if(sum %2==1) return false;
int target =sum/2;
vector<int> dp(target+1,0);//下标表示背包和 最大是targte 所以这边是target+1
dp[0]=true;
for(int i=0;i<nums.size();i++)
{
for(int j=target;j>=nums[i];j--)//注意这边的判断 剩余的背包数 要大于元素的大小
{
dp[j]=dp[j]||dp[j-nums[i]];
}
}
return dp[target];
}
};
1、最值问题: dp[i] = max/min(dp[i], dp[i-nums]+1)或dp[i] = max/min(dp[i], dp[i-num]+nums);
2、存在问题(bool):dp[i]=dp[i]||dp[i-num];
3、组合问题:dp[i]+=dp[i-num];
属于第三种情况,
问题的targe为正数和(主要是这边需要转换)
从nums数组中挑选数组成正数和,并且每个数字的位置是固定的,不存在顺序所以是组合
初始化 :一开始方式数都为0 只有dp【0】=1
一样的 递推方程两种状态,选择当前数 不选择当前数 ,dp[]表示方案数 不选择当前的方案数 选择当前的方案数
class Solution {
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 == 1) return 0; // 此时没有方案
if (nums.size() == 1 && sum < abs(S)) 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]+dp[j - nums[i]];
}
}
return dp[bagSize];
}
};
第一遍复习
x -y =target; x+y=sum; x = (sum+target)/2
第三遍复习:重新写了一遍 前面的推导就没有去弄了
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(target>sum) return 0;
if((target+sum)%2==1) return 0;
if (nums.size() == 1 && sum < abs(target)) return 0;//这个是一个特判 看评论
int bagSize= (target+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]+dp[j-nums[i]];
}
}
return dp[bagSize];
}
};
这道题看出是背包问题比较有难度
最后一块石头的重量:从一堆石头中,每次拿两块重量分别为x,y的石头,若x=y,则两块石头均粉碎;若x<y,两块石头变为一块重量为y-x的石头求最后剩下石头的最小重量(若没有剩下返回0)
问题转化为:把一堆石头分成两堆,求两堆石头重量差最小值
进一步分析:要让差值小,两堆石头的重量都要接近sum/2;我们假设两堆分别为A,B,A<sum/2,B>sum/2,若A更接近sum/2,B也相应更接近sum/2
进一步转化:将一堆stone放进最大容量为sum/2的背包,求放进去的石头的最大重量MaxWeight,最终答案即为sum-2*MaxWeight;、
0/1背包最值问题:外循环stones,内循环target=sum/2倒叙,应用转移方程1
class Solution {
public:
int lastStoneWeightII(vector<int>& stones)
{
//问题转变为最大重量 最大值题
//dp[j]表示当前背包可以放入的最大重量
int sum = accumulate(stones.begin(), stones.end(), 0);
int target = sum / 2;
vector<int> dp(target + 1);
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-2*dp[target];
}
};
总结就是1个变量需要2维,2个变量则需要3维
正常的是dp[j]=max(dp[j],dp[j-nums[i]+1])
本题的是:dp[i][j] = max(dp[i][j], dp[i - zero][j - one] + 1); 我们需要统计一下每个元素字符串中0的个数 和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));//初始化为0
for(auto str:strs)
{
int zero=0;//分别统计每一个物品0和1的个数 每次清0
int one=0;
//首先统计一下每一个物品中0和1的个数方便后面循环
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];
}
};
第一遍复习
第三遍复习:其实看一下 就很像背包问题 只是要做一些处理而已 没做过还真不清楚 看一下之前的代码 很容易理解,主要不好想。
数组内挑选元素 = target的
内层为正序背包 + 一个判断
class Solution {
public:
int coinChange(vector<int>& coins, int amount)
{
//这个s
//第二个是为了最后的判断 n没有一个符合的结果就是max 好判断
vector<long long> dp(amount+1,INT_MAX);
dp[0]=0;//价格为0需要0个硬币
for(int i =0;i<coins.size();i++)//外层遍历数组 内层遍历背包
{
for(int j=0;j<=amount;j++)
{
if(coins[i]<=j)//如果背包够大 不够大就不变
//不选当前硬币 选择当前硬币 数量+1,如果是截至就是加value(i)
{
dp[j]=min(dp[j],dp[j-coins[i]]+1);
}
}
}
if(dp[amount]==INT_MAX) return -1;
return dp[amount];
}
};
class Solution {
public:
int coinChange(vector<int>& coins, int amount)
{
// 1 是背包问题
// 2 完全背包 正序+判断
// 3 最小值 数量 nums 在外
// 4 初始化 特殊
vector<long long> dp(amount+1,INT_MAX);
dp[0]=0;
for(int i=0;i<coins.size();i++)
{
for(int j=0;j<=amount;j++)
{
if(j>=coins[i])
{
dp[j]=min(dp[j],dp[j-coins[i]]+1);
}
}
}
if(dp[amount]==INT_MAX) return -1;//这题的特殊处理
return dp[amount];
}
};
int i=0;i<=sqrt(n);i++
背包的元素是i*i 这边的i不再是数组的下标。vector dp(n+1,INT_MAX); dp[0]=0
class Solution {
public:
int numSquares(int n)
{
//dp肯定表示最小值 初始化就要为INT_MAX
vector<long long> dp(n+1,INT_MAX);
dp[0]=0;
//外层物品
for(int i=0;i<=sqrt(n);i++)
{
//内层背包 完全背包正序 加判断
for(int j=0;j<=n;j++)
{
//如果背包够放当前这个物品
if(j>=i*i)
{
//不选 选择
dp[j]=min(dp[j],dp[j-i*i]+1);
}
}
}
return dp[n];
}
};
class Solution {
public:
int numSquares(int n) {
vector<long long> dp(n+1,INT_MAX);
dp[0]=0;
// 外层 num 内存 target 正序
for(int i=0;i<=sqrt(n);i++)
{
for(int j=0;j<=n;j++)
{
if(j>=i*i)
{
dp[j]=min(dp[j],dp[j-i*i]+1);
}
}
}
return dp[n];
}
};
有目标 和 nums 标准的完全背包问题,返回的是方案数
1、最值问题: dp[i] = max/min(dp[i], dp[i-nums]+1)或dp[i] = max/min(dp[i], dp[i-num]+nums);
2、存在问题(bool):dp[i]=dp[i]||dp[i-num];
3、组合or排列问题:dp[i]=dp[i]+dp[i-num];
状态三:不选择当前元素,选择当前元素
这是完全背包中的方案数(排列问题)所以需要外层target 内层nums 外层正序
特别之处在于这个题测试样例有的通过不了 是超过了 INT_MAX 所以做了一个特判 并且 把int 换成 longlong
class Solution {
public:
int combinationSum4(vector<int>& nums, int target)
{
int n =nums.size();
vector<long long> dp(target+1,0);//dp表示方案数 初始都为0 如果完全不匹配 返回0
dp[0]=1;//和为0 只有一种可能
for(int j=1;j<=target;j++)
{
for(int i=0;i<n;i++)
{
if(j >= nums[i] && (dp[j]+dp[j-nums[i]] <INT_MAX) )//注意等于
{
dp[j]=dp[j]+dp[j-nums[i]];//不选择 选择
}
}
}
return dp[target];
}
};
第二遍复习
第三遍复习
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
没什么特别的就是从例子中区分这个题是组合非排序
class Solution {
public:
int change(int amount, vector<int> &coins)
{
vector<int> dp(amount + 1);
dp[0] = 1;
for (int coin : coins)
for (int i = 1; i <= amount; i++)
if (i >= coin)
dp[i] += dp[i - coin];
return dp[amount];
}
};
class Solution {
public:
int climbStairs(int n) {
vector<int> dp(n + 1, 0);
dp[0] = 1;
for (int i = 1; i <= n; i++) { // 遍历背包
for (int j = 1; j <= m; j++) { // 遍历物品
if (i - j >= 0) dp[i] += dp[i - j];
}
}
return dp[n];
}
};
class Solution {
public:
int climbStairs(int n) {
vector<int> dp(n+1,0);
dp[0]=1;
//完全背包中的排列问题
for(int j=0;j<=n;j++)//遍历背包
{
for(int i=1;i<=2;i++)//遍历物品
{
if(j>=i)
{
dp[j]=dp[j]+dp[j-i];
}
}
}
return dp[n];
}
};
target是正序+if的判断条件 这次是j要大于添加单词的长度
我对源代码进行修改 把 if的判断移到了下面其实更好理解 就是 dp[j]取决于不选择这个单词 和 选择了这个单词,选择这个单词并不是那么简单的还需要进行一个判断这个单词和背包字符串末尾是否匹配的上。所以变成如下,切记比对是背包后面的单词比对这样才能推到更小的背包考虑
dp[i] = dp[i] || s.substr(i - size, size) == word && dp[i - size];
正常的元素之和 就好像一段绳子 不需要比对 元素长度的绳子放在任意位置都是匹配的
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
vector<bool> dp(s.size() + 1);
dp[0] = true;
for(int i = 1; i <= s.size(); i++){
for(auto& word: wordDict){
int size = word.size();
if (i - size >= 0 )
dp[i] = dp[i] || s.substr(i - size, size) == word && dp[i - size];
}
}
return dp[s.size()];
}
};
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict)
{
vector<bool> dp(s.size()+1);
dp[0]=true;//不选就可以组成
//完全背包 正序 排序 外层是target
for(int j=0;j<=s.size();j++)
{
for(auto word:wordDict)
{
int size =word.size();
if(j>=size)//表示可以放入
{
dp[j]=dp[j]||dp[j-size] && s.substr(j-size,size)==word;;//不放入 放入
}
}
}
return dp[s.size()];
}
};
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
//很明显是完全背包的排序问题 下标表示长度
vector<bool> dp(s.size()+1);
dp[0]=true;//下标其实就是 长度为0是可以的
for(int j=1;j<=s.size();j++)//外层是target
{
for(auto word:wordDict)
{
int size=word.size();
if(j>=size)//表示可以放在
{
dp[j]=dp[j]||dp[j-size] && s.substr(j-size,size)==word;;//不放入 放入
}
}
}
return dp[s.size()];
}
};
如有错误,欢迎指出,排版不是很好,主要以自己的记录复习为主。