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

[dp22_二维背包] 一和零 | 盈利计划

目录

1.一和零

题解

2.盈利计划

题解


1.一和零

链接: 474. 一和零

给你一个二进制字符串数组 strs 和两个整数 mn

请你找出并返回 strs 的最大子集的长度,该子集中 最多m0n1

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y子集

示例 1:

输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

示例 2:

输入:strs = ["10", "0", "1"], m = 1, n = 1
输出:2
解释:最大的子集是 {"0", "1"} ,所以答案是 2 。

  • 找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1
  • 也就说找到的子集 字符 0 的个数 小于等于 m,字符 1 的个数 小于等于 n。

如果对背包问题敏感,这就是一个背包问题。

  • 背包问题问的是给一堆物品,让在这堆物品中选一些物品,在满足某个限定条件下问一些东西,要么问最大价值,要么问多少种选法。
  • 这道题其实就是背包问题,就是在字符串数组中挑一些字符串出来,需要满足的是两个条件
  • 字符 0 的个数小于等于5,字符 1 的个数小于等于3。
  • 像这种满足两个条件的背包问题,我们称之为二维费用背包问题。

二维费用背包问题:

之前我们遇到的01背包问题,完全背包问题都只有一个限定条件,二维费用背包就是多了一个限制变成了两个限制条件

  • 二维费用背包问题也分为二维费用的01背包问题和二维费用的完全背包问题。
  • 这这道题因为每个字符串都面临选or不选两种情况,所以是二维费用的01背包问题。

题解

之前说过所有其余分类的背包问题都是从01背包问题哪里延申出来的。

  • 所以我们尝试用01背包问题分析思路来解决我们这里的问题。

1.状态表示

  • 01背包状态表示:
  • dp[i][j] 表示:从前 i 个物品中挑选,总体积不超过 j,所有选法中,最大的价值
  • 二维费用的01背包问题:其实就只是多加了一个限制条件,之前就只有体积限制,现在不仅有体积还有重量。所以多加一维。
  • dp[i][j][k] 表示:从前 i 个字符串中挑选,字符 0 的个数不超过 j,字符 1 的个数不超过 k,所有选法中,最长的长度。

2.状态转移方程

  • 本质还是一个01背包问题,所以我们还可以借用之前01背包状态转移方程的思路,根据最后一个位置,划分情况
  • 设最后一个位置的字符串中,字符 0 的个数为 a,字符 1 的个数为 b。
  • 不选 i 位置这个字符串,那就从 0 ~ i - 1 区间内字符串中挑,因为 i 位置都没选,所以0 ~ i - 1 区间内字符串中选 字符 0 的个数不超过 j,字符 1 的个数不超过k,在这两个限制条件下要的最大的长度。就是dp[i-1][j][k]

选 i 位置这个字符串,整体要保证字符 0 的个数不超过 j,字符 1 的个数不超过k,又因为选了 i 位置这个字符串,接下去取 0 ~ i - 1挑的时候,就要去掉 i 这个字符串中 字符串 0 的个数 和 字符串 1 的个数。然后加上选 i 位置字符串的个数。

  • 注意 j - a,k - b 不一定存在,所以用之前要判断一下。
  • 我们要的是最大长度,因此是两种情况的最大值

  • 如果01背包问题都熟悉了,二维费用的01背包问题就是多加一个限制条件其他都一样。

3.初始化

  • 初始化和之前一样,仅需考虑 i = 0 的情况就可以了k = 0,j = 0 我们会特判不会越界的。
  • 当 i = 0 表示字符串数组里没有字符串,如果数组没有字符串,想凑成字符串 0 的个数不超过 j,字符串 1 的个数不超过 k ,根本不能做到,不能做到就是一个空集
  • 如果是空集,那最大个数全都是0

4.填表顺序

  • 填dp[i][j][k]状态,要保证填 i 这一面 , i - 1 那一面的值已经填好了
  • 因此仅需保证 i 从小到大。j、k无论怎么变化什么顺序都可以。

5.返回值

  • dp[i][j][k] 表示:从前 i 个字符串中挑选,字符 0 的个数不超过 j,字符 1 的个数不超过 k,所有选法中,最长的长度。
  • 我们要的是从整个字符串中选,字符 0 的个数不超过 j,字符 1 的个数不超过 k,所有选法中,最长的长度。所以返回的是
    dp[len][m][n],len是字符串数组长度。
class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {int len=strs.size();vector<vector<vector<int>>> dp(len+1,vector<vector<int>>(m+1,vector<int>(n+1,0)));//0 个 元素 初始化为0for(int i=1;i<=len;i++){for(int j=0;j<=m;j++){for(int k=0;k<=n;k++){int a=0,b=0;//下标映射for(char& c:strs[i-1]){if(c=='0')a++;if(c=='1')b++;}dp[i][j][k]=dp[i-1][j][k];if(j>=a && k>=b)dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-a][k-b]+1);}}}return dp[len][m][n];}
};
  • 子集 要是 连续的,所以可以这么写

优化

  • 只看 上一行一列
  • 01 背包,避免覆盖,要从后往前
class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {int len=strs.size();vector<vector<int>> dp(m+1,vector<int>(n+1,0));//0 个 元素 初始化为0for(int i=1;i<=len;i++){for(int j=m;j>=0;j--){for(int k=n;k>=0;k--){int a=0,b=0;//下标映射for(char& c:strs[i-1]){if(c=='0')a++;if(c=='1')b++;}if(j>=a && k>=b)    dp[j][k]=max(dp[j][k],dp[j-a][k-b]+1);}}}return dp[m][n];}
};

2.盈利计划

链接: 879. 盈利计划

集团里有 n 名员工,他们可以完成各种各样的工作创造利润。

i 种工作会产生 profit[i] 的利润,它要求 group[i] 名成员共同参与。如果成员参与了其中一项工作,就不能参与另一项工作。

工作的任何至少产生 minProfit 利润的子集称为 盈利计划 。并且工作的成员总数最多为 n

有多少种计划可以选择?因为答案很大,所以 返回结果模 10^9 + 7 的值

示例 1:

输入:n = 5, minProfit = 3, group = [2,2], profit = [2,3]
输出:2
解释:至少产生 3 的利润,该集团可以完成工作 0 和工作 1 ,或仅完成工作 1 。
总的来说,有两种计划。

示例 2:

输入:n = 10, minProfit = 5, group = [2,3,5], profit = [6,7,8]
输出:7
解释:至少产生 5 的利润,只要完成其中一种工作就行,所以该集团可以完成任何工作。
有 7 种可能的计划:(0),(1),(2),(0,1),(0,2),(1,2),以及 (0,1,2) 。

这道题读起来很难读,但是结合示例我们能明白这道题的意思:

  • 集团有n名员工,还有一些计划,每种计划都有对应的利润以及人数。
  • 问从这些计划种挑选一些计划,要求人数不超过n,利润至少为minProfit
  • 一共有多少种计划可以选择。

当明白这道题的意思之后,我们就应该想到这是一道背包问题。

  • 背包问题就是给我们一些东西,让我们在这些东西里挑一些东西出来,在满足某个限定条件下,问一些东西。这道题问的是有多少计划可以选择。
  • 这道题有两个限定条件,因此是一个二维费用的背包问题,又因为每种工作只能面临选or不选两个情况,所以这是一个二维费用的01背包问题。

题解

1.状态表示

  • dp[i][j][k] 表示:从前 i 个计划中挑选,总人数不超过 j,总利润至少为 k,一共有多少种选法。
  • 不超过j -> 小于等于j,至少为k -> 大于等于k
  • 等会状态转移方程你会发现不超过和至少状态转移方程是不一样的。

2.状态转移方程

  • 还是根据最后一个位置,划分情况
  • 不选 i ,相当于去 0 ~ i - 1 去看看总人数不超过 j,利润不超过 k,在这两种情况下,一共有多少种情况

选 i,要求总人数不超过 j,如果选 i 位置的计划,i 位置里面有个人数就是group[i]里面存着,那去 0 ~ i - 1里面挑的时候总人数是不能超过 j - group[i]的,

  • 先别着急分析下一个,这里思考一下 j - g[i] 能小于0吗?不能计划就已经超过总人数了,那前面在怎么挑也不行。
  • 在分析第二个限定条件,总利润至少为k,选 i 位置的时候,i 有一个利润在profit[i]里存着,也就是说在 0 ~ i -1 里面去选总利润至少为 k - p[i]就够了,看似和上面一样,但是k - p[i] 能小于 0 吗?
  • 但是要注意,刚才的状态表示,一个是不超过 j,一个是至少为 k。其实 k - p[i] 能小于 0也就相当于 k < p[i] ,也就是说在 0 ~ i 里挑总利润大于等于K,利润多了就多了呗

但是问题来了dp是一个数组,数组下标能小于0吗?不能!也就是说 k - p[i] 是可以存在的,但是在数组中找不到。

  • 所以我们要根据实际情况想一个折中的想法来表示这种情况。
  • i 位置利润就已经超过k了,那说明前面 0 ~ i -1 里面的利润挑多少都可以,所以我们可以认为前面利润只要大于等于0就可以了。

问一共多少选法,所以这两种情况加起来

3.初始化

这里不在画图了,可以根据实际情况来初始化。

  • 这里我们仅需出来当没有任务的时候,dp[i][j][k] 应该等于多少。
  • 如果没有任务,那也没有利润,此时不管有多少人,只要不选就好了
  • 也就是选出来一个空集也就是一种选法,因此dp[0][j][0] = 1 (0 <= j <= n)

4.填表顺序

  • i 需要 i - 1,所以仅需要保证 i 从小到大

5.返回值

  • dp[i][j][k] 表示:从前 i 个计划中挑选,总人数不超过 j,总利润至少为 k,一共有多少种选法。
  • 我们要的是从所有计划中挑,总人数不超过 n,总利润至少为 m,一共有多少种选法。
  • 因此返回 dp[len][n][m], len是数组的长度
class Solution {typedef long long ll;
public:int profitableSchemes(int n, int m, vector<int>& group, vector<int>& profit) {int w=group.size();vector<vector<vector<ll>>> dp(w+1,vector<vector<ll>>(n+1,vector<ll>(m+1)));//针对 dp[0][j][0]=1 //没工作 有人 没利润  //不要人 也是一种选法for(int j=0;j<=n;j++)dp[0][j][0]=1;for(int i=1;i<=w;i++){//xia biaofor(int j=0;j<=n;j++){for(int k=0;k<=m;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]%=(ll)(1e9+7);}}}return dp[w][n][m]; }
};

优化

  • 01 背包,倒着优化
class Solution {typedef long long ll;
public:int profitableSchemes(int n, int m, vector<int>& group, vector<int>& profit) {int w=group.size();vector<vector<ll>> dp(n+1,vector<ll>(m+1));//针对 dp[0][j][0]=1 //没工作 有人 没利润for(int j=0;j<=n;j++)dp[j][0]=1;for(int i=1;i<=w;i++){//xia biaofor(int j=n;j>=group[i-1];j--){for(int k=m;k>=0;k--){dp[j][k]+=dp[j-group[i-1]][max(0,k-profit[i-1])];dp[j][k]%=(ll)(1e9+7);}}}return dp[n][m]; }
};

相关文章:

  • Python网络爬虫设计(三)
  • 【QT】 QT中的列表框-横向列表框-树状列表框-表格列表框
  • 【JavaWeb后端开发02】SpringBootWeb + Https协议
  • vuex实现同一页面radio-group点击不同按钮显示不同表单
  • Redis——内存策略
  • 数据通信学习笔记之OSPF的邻居角色
  • 【漫话机器学习系列】213.随机梯度下降(SGD)
  • 大学之大:布里斯托大学2025.4.20
  • From RAG to Memory: Non-Parametric Continual Learning for Large Language Models
  • SpringCloud实战
  • 征程 6 VIO 通路断流分析
  • 内容合作方资源哪里找?如何管理?
  • 每日面试实录·携程·社招·JAVA
  • 牛客 | OJ在线编程常见输入输出练习
  • Java中订阅消费模式(发布-订阅模式)和观察者模式的区别
  • 2025年渗透测试面试题总结-拷打题库08(题目+回答)
  • Java8-遍历list取出两个字段重新组成list集合
  • FreeSWITCH 简单图形化界面41 - 批量SIP视频呼叫测试
  • SQL注入之information_schema表
  • 浅聊docker的联合文件系统
  • 复旦大学史地学系在北碚
  • 以优良作风激发改革发展动力活力,中管企业扎实开展深入贯彻中央八项规定精神学习教育
  • 鲁比奥称“美或退出俄乌谈判”,欧洲官员:为了施压乌克兰
  • 管理规模归零,华夏基金“ETF规模一哥”张弘弢清仓卸任所有产品
  • 南华期货递表港交所,冲刺第二家“A+H”股上市期货公司
  • 张巍|另眼看古典学⑩:再创作让古希腊神话重获生机——重述厄勒克特拉