当前位置: 首页 > news >正文

【动规】背包问题

01背包问题:

一. 01背包(牛客网)

在这里插入图片描述
在这里插入图片描述
01背包问题的本质就是一个商品选不选,同时注意背包的容量问题,那么状态表示一般用二维,一维来表示选不选一维来表示容量,要注意背包体积是否有剩余,完全背包问题要注意体积是否刚好合适,初始化也是采用多增加一行一列来避免越界问题

#include <iostream>
#include <cstring>
using namespace std;const int N=1010;
int n,V,v[N],w[N];
int dp[N][N];
int main() {//获取数据cin>>n>>V;for(int i=1;i<=n;i++)cin>>v[i]>>w[i];//解决第一问for(int i=1;i<=n;i++)for(int j=0;j<=V;j++){dp[i][j]=dp[i-1][j];if(j-v[i]>=0)dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);}//第一问答案输出cout<<dp[n][V]<<endl;//解决第二问memset(dp,0,sizeof(dp));//初始化第一行为-1的情况for(int i=1;i<=V;i++) dp[0][i]=-1;for(int i=1;i<=n;i++)for(int j=0;j<=V;j++){dp[i][j]=dp[i-1][j];if(j-v[i]>=0&&dp[i-1][j-v[i]]!=-1)dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);}cout<<(dp[n][V]==-1?0:dp[n][V])<<endl;return 0;
}
  • 优化版本:
    已知填表需要用到上一行的信息,用完之后就没有了,所以可以极限一点,只用一行一维数组来存储,但是遍历顺序要改为从后往前,因为一维数组中,dp[j] 既存储 i-1 层的旧状态,又要存储 i 层的新状态。正序遍历 j 时,较小的 j 会先被更新为 i 层状态(选了当前物品)。当计算较大的 j 时,j - v[i] 可能指向已经更新过的 i 层状态(而非原始的 i-1 层),导致同一物品被多次计入(违背 01 背包 “每个物品只能选一次” 的规则),而逆序遍历从大的位置开始覆盖,能保证只使用上一层的旧状态
  • 方法:
    所有的背包问题都可以进行空间上的优化
    1.直接在原始代码上修改,删除第一维数组即可
    2.改变j的遍历顺序
#include <iostream>
#include <cstring>
using namespace std;const int N=1010;
int n,V,v[N],w[N];
int dp[N][N];
int main() {//获取数据cin>>n>>V;for(int i=1;i<=n;i++)cin>>v[i]>>w[i];//解决第一问for(int i=1;i<=n;i++)for(int j=0;j<=V;j++){dp[i][j]=dp[i-1][j];if(j-v[i]>=0)dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);}//第一问答案输出cout<<dp[n][V]<<endl;//解决第二问memset(dp,0,sizeof(dp));//初始化第一行为-1的情况for(int i=1;i<=V;i++) dp[0][i]=-1;for(int i=1;i<=n;i++)for(int j=0;j<=V;j++){dp[i][j]=dp[i-1][j];if(j-v[i]>=0&&dp[i-1][j-v[i]]!=-1)dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);}cout<<(dp[n][V]==-1?0:dp[n][V])<<endl;return 0;
}

二. 分割等和子集

在这里插入图片描述
在这里插入图片描述
可以将该问题转化为背包问题,选每个数就相当于在选商品,等和子集就相当于背包容量,注意容量要是偶数才能划分否则直接返回false,注意初始化第一列可以的情况

class Solution {
public:bool canPartition(vector<int>& nums) {int n=nums.size();int sum=0;for(auto e:nums) sum+=e;if(sum%2==1) return false;int s=sum/2;vector<vector<bool>>dp(n+1,vector<bool>(s+1));//初始化for(int i=1;i<=n;i++) dp[i][0]=1;//填表for(int i=1;i<=n;i++)for(int j=1;j<=s;j++){dp[i][j]=dp[i-1][j];if(j>=nums[i-1]) dp[i][j]=dp[i][j]||dp[i-1][j-nums[i-1]];}return dp[n][s];}
};

空间优化:
1.删掉第⼀维;
2.修改第⼆层循环的遍历顺序即可,从后往前

class Solution {
public:bool canPartition(vector<int>& nums) {int n=nums.size();int sum=0;for(auto e:nums) sum+=e;if(sum%2==1) return false;int s=sum/2;vector<bool>dp(s+1);//初始化dp[0]=1;//填表for(int i=1;i<=n;i++)for(int j=s;j>=nums[i-1];j--){dp[j]=dp[j]||dp[j-nums[i-1]];}return dp[s];}
};

三. (494.) 目标和

在这里插入图片描述
在这里插入图片描述
关键点在于如何转化成背包问题,后面的分析思路就比较统一,本质就是在数组中选一些数能否等于目标值。
注意初始化:
由于需要用到上⼀行的数据,因此我们可以先把第⼀行初始化。第⼀行表示不选择任何元素,要凑成目标和 j 。只有当目标和为 0 的时候才能做到,因此第⼀
行仅需初始化第⼀个元素 dp[0][0] = 1

class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int s=0,sum=0;int n=nums.size();for(auto e:nums) s+=e;sum=(s+target)/2;if(sum<0||(s+target)%2==1)return 0;vector<vector<int>>dp(n+1,vector<int>(sum+1));dp[0][0]=1;for(int i=1;i<=n;i++)for(int j=0;j<=sum;j++){dp[i][j]=dp[i-1][j];if(j>=nums[i-1]) dp[i][j]+=dp[i-1][j-nums[i-1]];}return dp[n][sum];}
};

空间优化:

class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int s=0,sum=0;int n=nums.size();for(auto e:nums) s+=e;sum=(s+target)/2;if(sum<0||(s+target)%2==1)return 0;vector<int>dp(sum+1);dp[0]=1;for(int i=1;i<=n;i++)for(int j=sum;j>=nums[i-1];j--){dp[j]+=dp[j-nums[i-1]];}return dp[sum];}
};

四. (1049.) 最后一块石头的重量 II

在这里插入图片描述
可以将问题转化为01背包问题,已知任意两块石头放在一起重量相同会被丢弃,重量不同会进行差值计算并保留,也就是说任意两块石头重量相同部分会被丢弃,重量差异部分相当于在原数据前加上加减符号,所以可以将该题转化为上题目标和一样的思路
当所有元素的和固定时,分成的两部分越接近数组总和的⼀半,两者的差越小。因此问题就变成了:在数组中选择⼀些数,让这些数的和尽量接近 sum / 2 ,如果把数看成物品,每个数的值看成体积和价值,问题就变成了01 背包问题
在这里插入图片描述

class Solution {
public:int lastStoneWeightII(vector<int>& s) {int sum=0;int n=s.size();for(auto e:s) sum+=e;int aim=sum/2;vector<vector<int>>dp(n+1,vector<int>(aim+1,0));for(int i=1;i<=n;i++)for(int j=0;j<=aim;j++){dp[i][j]=dp[i-1][j];if(j>=s[i-1]) dp[i][j]=max(dp[i][j],dp[i-1][j-s[i-1]]+s[i-1]);}return sum-2*dp[n][aim];}
};
  • 空间优化版本:
class Solution {
public:int lastStoneWeightII(vector<int>& s) {int sum=0;int n=s.size();for(auto e:s) sum+=e;int aim=sum/2;vector<int>dp(aim+1,0);for(int i=1;i<=n;i++)for(int j=aim;j>=s[i-1];j--){dp[j]=max(dp[j],dp[j-s[i-1]]+s[i-1]);}return sum-2*dp[aim];}
};

完全背包问题:

五. 完全背包

在这里插入图片描述
在这里插入图片描述

  • 关于空间优化01背包和完全背包的区别:
    都是通过空间覆盖来减少空间消耗,区别在于遍历顺序,遍历顺序的本质是状态依赖方向。01背包怕重复选,所以从右往左保护旧状态,完全背包需要重复选,所以从左往右利用新状态
#include <iostream>
#include<cstring>
using namespace std;const int N=1010;
int dp[N][N],v[N],w[N];
int main() {//获取输入int n, V;cin>>n>>V;for(int i=0;i<n;i++) cin>>v[i]>>w[i];//第一题for(int i=1;i<=n;i++)for(int j=0;j<=V;j++){dp[i][j]=dp[i-1][j];if(j>=v[i-1])dp[i][j]=max(dp[i][j],dp[i][j-v[i-1]]+w[i-1]);}cout<<dp[n][V]<<endl;//第二问memset(dp,0,sizeof(dp));for(int i=1;i<=V;i++)dp[0][i]=-1;for(int i=1;i<=n;i++)for(int j=0;j<=V;j++){dp[i][j]=dp[i-1][j];if(j>=v[i-1]&&dp[i][j-v[i-1]]!=-1)dp[i][j]=max(dp[i][j],dp[i][j-v[i-1]]+w[i-1]);}cout<<(dp[n][V]==-1?0:dp[n][V])<<endl;return 0;
}
  • 空间优化版本:
#include <iostream>
#include<cstring>
using namespace std;const int N=1010;
int dp[N],v[N],w[N];
int main() {//获取输入int n, V;cin>>n>>V;for(int i=0;i<n;i++) cin>>v[i]>>w[i];//第一题for(int i=1;i<=n;i++)for(int j=v[i-1];j<=V;j++){dp[j]=max(dp[j],dp[j-v[i-1]]+w[i-1]);}cout<<dp[V]<<endl;//第二问memset(dp,0,sizeof(dp));for(int i=1;i<=V;i++)dp[i]=-1;for(int i=1;i<=n;i++)for(int j=v[i-1];j<=V;j++){if(dp[j-v[i-1]]!=-1)dp[j]=max(dp[j],dp[j-v[i-1]]+w[i-1]);}cout<<(dp[V]==-1?0:dp[V])<<endl;return 0;
}

六. (322.) 零钱兑换

在这里插入图片描述
在这里插入图片描述

class Solution {
public:int coinChange(vector<int>& coins, int amount) {int n=coins.size();vector<vector<int>>dp(n+1,vector<int>(amount+1));//初始化for(int i=1;i<=amount;i++) dp[0][i]=0x3f3f3f3f;for(int i=1;i<=n;i++)for(int j=0;j<=amount;j++){dp[i][j]=dp[i-1][j];if(j>=coins[i-1]) dp[i][j]=min(dp[i][j],dp[i][j-coins[i-1]]+1);}return dp[n][amount]>=0x3f3f3f3f?-1:dp[n][amount];}
};

空间优化:

class Solution {
public:int coinChange(vector<int>& coins, int amount) {int n=coins.size();vector<int>dp(amount+1);//初始化for(int i=1;i<=amount;i++) dp[i]=0x3f3f3f3f;for(int i=1;i<=n;i++)for(int j=coins[i-1];j<=amount;j++){dp[j]=min(dp[j],dp[j-coins[i-1]]+1);}return dp[amount]>=0x3f3f3f3f?-1:dp[amount];}
};

七. (518.) 零钱兑换 II

在这里插入图片描述
在这里插入图片描述

class Solution {
public:int change(int amount, vector<int>& coins) {int n=coins.size();vector<vector<unsigned long long>>dp(n+1,vector<unsigned long long>(amount+1));dp[0][0]=1;for(int i=1;i<=n;i++)for(int j=0;j<=amount;j++){dp[i][j]=dp[i-1][j];if(j>=coins[i-1]) dp[i][j]+=dp[i][j-coins[i-1]];}return dp[n][amount];}
};

八. (279.) 完全平方数

在这里插入图片描述
在这里插入图片描述

class Solution {
public:int numSquares(int n) {int m=sqrt(n);vector<int>dp(n+1,0x3f3f3f3f);dp[0]=0;for(int i=1;i<=m;i++)for(int j=i*i;j<=n;j++){dp[j]=min(dp[j],dp[j-i*i]+1);}return dp[n];}
};

二维费用的背包问题:

九. (474.) ⼀和零

在这里插入图片描述
在这里插入图片描述

class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {int k=strs.size();vector<vector<vector<int>>>dp(k+1,vector<vector<int>>(m+1,vector<int>(n+1)));for(int i=1;i<=k;i++){//统计0(a)、1(b)个数int a=0,b=0;for(auto e:strs[i-1]){if(e=='0') a++;else b++;}for(int j=0;j<=m;j++)for(int x=0;x<=n;x++){dp[i][j][x]=dp[i-1][j][x];if(j>=a&&x>=b)dp[i][j][x]=max(dp[i][j][x],dp[i-1][j-a][x-b]+1);}}return dp[k][m][n];}
};

空间优化:
所有的背包问题,都可以进⾏空间上的优化
对于⼆维费⽤的 01 背包类型的,优化策略是:
i. 删掉第⼀维;
ii. 修改第⼆层以及第三层循环的遍历顺序即可

class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {int k=strs.size();vector<vector<int>>dp(m+1,vector<int>(n+1));for(int i=1;i<=k;i++){//统计0(a)、1(b)个数int a=0,b=0;for(auto e:strs[i-1]){if(e=='0') a++;else b++;}for(int j=m;j>=a;j--)for(int x=n;x>=b;x--)  dp[j][x]=max(dp[j][x],dp[j-a][x-b]+1);}return dp[m][n];}
};

十. (879.) 盈利计划

在这里插入图片描述
在这里插入图片描述
第三维盈利越多越好肯定,所以k-price[i-1]可能会小于0,但是数组下标不能用负数来表示,所以用0来统一表示下标为负数的情况,取max如果盈利目标-当前盈利小于0就取0,大于0就取本身

class Solution {
public:int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) {  const int mod=1e9+7;int m=group.size();vector<vector<vector<int>>>dp(m+1,vector<vector<int>>(n+1,vector<int>(minProfit+1)));//初始化,选择为空集的情况for(int i=0;i<=n;i++) dp[0][i][0]=1;//填表for(int i=1;i<=m;i++)for(int j=0;j<=n;j++)for(int k=0;k<=minProfit;k++){dp[i][j][k]=dp[i-1][j][k];if(j>=group[i-1])dp[i][j][k]+=dp[i-1][j-group[i-1]][max(0,k-profit[i-1])];dp[i][j][k]%=mod;}return dp[m][n][minProfit];}
};

空间优化:

class Solution {
public:int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) {  const int mod=1e9+7;int m=group.size();vector<vector<int>>dp(n+1,vector<int>(minProfit+1));//初始化,选择为空集的情况for(int i=0;i<=n;i++) dp[i][0]=1;//填表for(int i=1;i<=m;i++)for(int j=n;j>=group[i-1];j--)for(int k=minProfit;k>=0;k--){dp[j][k]+=dp[j-group[i-1]][max(0,k-profit[i-1])];dp[j][k]%=mod;}return dp[n][minProfit];}
};

似包非包问题:

十一.(377.) 组合总和 Ⅳ

在这里插入图片描述
在这里插入图片描述

  • 背包问题本质求的是组合问题,是无序的,该题是排列问题,是有序的,不能用背包问题的思路来解决,用常规的动规思路来解决.dp[i]通过最后一个元素的位置,依次遍历整个原数组,若小于当前索引值就把dp[i-1]的情况累加起来
class Solution {
public:int combinationSum4(vector<int>& nums, int target) {vector<double>dp(target+1);dp[0]=1;for(int i=1;i<=target;i++)for(auto e:nums)if(i>=e) dp[i]+=dp[i-e];return dp[target];}
}; 

十二. (96.) 不同的二叉搜索树

在这里插入图片描述
在这里插入图片描述
从1到当前下标位置依次遍历每一个元素作为根节点时有多少个二叉树。
外层循环 i 表示 “当前要组成的 BST 有 i 个节点(用 1到i 这 i 个数)”;内层循环 j 表示 “选择 j 作为当前 BST 的 根节点”(j 必须是 1到i 中的某个数,因为 BST 的节点值是 1~i 连续的),0不能当作根节点,与题目要求不符,不是合法节点值,也会造成左子树计算越界

class Solution {
public:int numTrees(int n) {vector<int>dp(n+1);dp[0]=1;for(int i=1;i<=n;i++)for(int j=1;j<=i;j++)dp[i]+=dp[j-1]*dp[i-j];return dp[n];}
};

总结:

  • 关于状态转移方程的优化:
  • 01背包:
    商品只能选一次,依赖上一层的商品状态,所以状态转移方程一般形如:
    dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i])
  • 完全背包:
    可以重复选择商品,所以可以依赖当前层状态,由于重复选择所以无法穷尽状态转移方程,可采用数学等价替换的方式来优化表达式。一般完全背包的优化方式如下:
    在这里插入图片描述
  • 关于空间优化:
  • 01背包:
    将第一维去掉,利用滚动数组的方式来覆盖计算。改变遍历顺序,从右往左,因为只能选一次商品,依赖上一层的状态,如果从左往右计算可能导致状突被提前覆盖,从右往左就不会
  • 完全背包:
    将第一维去掉,利用滚动数组的方式来覆盖计算。遍历顺序可以不用改变,因为可以重复选商品,所以不怕当前层元素被覆盖
http://www.dtcms.com/a/609510.html

相关文章:

  • js:网页屏幕尺寸小于768时,切换到移动端页面
  • 《LLM零开销抽象与插件化扩展指南》
  • C++_面试题_21_字符串操作
  • 多重组合问题与矩阵配额问题
  • 什么情况下会把 SYN 包丢弃?
  • EG27324 带关断功能双路MOS驱动芯片技术解析
  • do_action wordpress 模板关键词优化排名的步骤
  • 海外网站入口通信管理局 网站备案
  • 在 Java 中实现 Excel 数字与文本转换
  • 如何保持不同平台的体验一致性
  • redis(五)——管道、主从复制
  • OBS直播教程:OBS实时字幕插件如何下载?OBS实时字幕插件如何安装?OBS实时字幕插件如何使用?OBS实时字幕插件官方下载地址
  • WPF中TemplatePart机制详解
  • 大学生毕业设计课题做网站网站开发研发设计
  • PPT制作正在发生一场静默革命
  • 无线通信信道的衰落特性
  • 大模型量化压缩实战:从FP16到INT4的生产级精度保持之路
  • ListDLLs Handle 学习笔记(8.11):谁注入了 DLL?谁占着文件不放?一篇教你全搞定
  • 电子电气架构 ---软件架构的准则与描述
  • linux下网站搭建wordpress文章页图片尺寸
  • 上海集团网站建设公司好蚌埠的网站建设
  • opencv 学习: QA_01 什么是图像锐化
  • C++标准库中的排序算法
  • 做网站图片和文字字体侵权seo是什么意思金融
  • Node.js npm 安装过程中 EBUSY 错误的分析与解决方案
  • 科普:华为星闪是什么?华为星闪(英文名 NearLink)是国际星闪无线短距通信联盟发布的新型无线短距通信标准技术。
  • 数据结构6:排序
  • 解决 npm 依赖版本冲突:从 “unable to resolve dependency tree“ 到依赖管理高手
  • Ubuntu 使用 Python 启动 HTTP 服务
  • day14(11.14)——leetcode面试经典150