动态规划专题:详解二维费用背包问题——以“一和零”与“盈利计划”为例

56.一和零

一和零

给你一个二进制字符串数组 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 。

提示:

  • 1 <= strs.length <= 600
  • 1 <= strs[i].length <= 100
  • strs[i] 仅由 '0''1' 组成
  • 1 <= m, n <= 100

如果是01背包的话

那么dpi j表示的就是:从前i个物品中挑选,总体积不超过j,所有选法中,最大的价值

二维费用

又多了一个限制条件

dpi j k :从前i个字符串中挑选,0的个数不超过j,字符1的个数不超过k,所有的选法中,最大的长度

C++ 复制代码
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)));

        //我们这里是不需要进行初始化操作的

        for(int i=1;i<=len;i++)//遍历字符串的

        {

            //统计0和1的个数

            int a=0,b=0;//a:当前考虑的第 `i` 个字符串中 0 的数量,b:当前考虑的第 `i` 个字符串中 1 的数量

            for(auto ch:strs[i-1])//我们这里是需要进行-1进行数组映射的,因为我们多加了一列的

            {

                //遍历strs中的每一个字符串里面的字符

                if(ch=='0')a++;

                else b++;

            }

            for(int j=0;j<=m;j++)

            {

                for(int k=0;k<=n;k++)

                {

                    dp[i][j][k]=dp[i-1][j][k];//这个是我们i位置不选的情况

                    if(j>=a&&k>=b)
                    //这个条件判断的作用是检查当前可用的 0 和 1 的数量是否足够选择第 `i` 个字符串。只有当当前可用的 0 的数量 `j` 大于等于第 `i` 个字符串中 0 的数量 `a`,并且当前可用的 1 的数量 `k` 大于等于第 `i` 个字符串中 1 的数量 `b` 时,才可以选择第 `i` 个字符串。

                        dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-a][k-b]+1);

                }

            }

        }

        return dp[len][m][n];

    }

};

dp[i - 1][j - a][k - b] 表示在前 i - 1 个字符串中选择,在 0 的数量不超过 j - a 且 1 的数量不超过 k - b 的情况下所能得到的最大子集的字符串个数,因为选择了第 i 个字符串,所以要在这个基础上加 1。

max(dp[i][j][k], dp[i - 1][j - a][k - b] + 1):取不选择第 i 个字符串和选择第 i 个字符串这两种情况中的最大值,更新 dp[i][j][k] 的值。

下面的代码是空间优化

这里是模仿01背包的模版,可以按照01背包的样例进行空间优化 的

C++ 复制代码
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));

        //我们这里是不需要进行初始化操作的

        for(int i=1;i<=len;i++)//遍历字符串的

        {

            //统计0和1的个数

            int a=0,b=0;//a表示0的个数,b表示1个数

            for(auto ch:strs[i-1])//我们这里是需要进行-1进行数组映射的,因为我们多加了一列的

            {

                //遍历strs中的每一个字符串里面的字符

                if(ch=='0')a++;

                else b++;

            }

            for(int j=m;j>=a;j--)

            {

                for(int k=n;k>=b;k--)

                {

                    dp[j][k]=max(dp[j][k],dp[j-a][k-b]+1);

                }

            }

        }

        return dp[m][n];

    }

};

57.盈利计划

盈利计划

集团里有 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) 。

提示:

  • 1 <= n <= 100
  • 0 <= minProfit <= 100
  • 1 <= group.length <= 100
  • 1 <= group[i] <= 100
  • profit.length == group.length
  • 0 <= profit[i] <= 100

第0种工作会产生2种利润,需要两名员工

第1种工作会产生3种利润,需要两名员工

说白了就是从g和p数组中挑选,挑选的值要满足右边的条件,就是参与工作的人数要小于n

利润要大于minprofit

这里的话条件需要满足两个,并且是一个01背包问题,

所以是二位费用的背包问题

这里的工作只能是选和不选两种情况,所以的话是01背包而不是完全背包

dpi j k:表示:从前i个计划中挑选,总人数不超过j,总利润至少为k,一共有多少种选法

如果我们i位置的利润大于等于k了,那么我们就在i-1区域里面满足利润大于等于0就行了

初始化:当我们没有任务的时候并且没有利润,无论人数有多少,我只有一种选择,就是一个空集

返回值

dplen n m

C++ 复制代码
class Solution

{

public:

    int profitableSchemes(int n, int m, vector<int>& g, vector<int>& p)

    {

        const int MOD=1e9+7;//取模的数

        int len=g.size();

        vector<vector<vector<int>>>dp(len+1,vector<vector<int>>(n+1,vector<int>(m+1)));

  

        for(int j=0;j<=n;j++)dp[0][j][0]=1;//没有工作,没有利润的情况下,无论人数有多少都是空集的,只存在空集这一种情况的

  

        for(int i=1;i<=len;i++)

        {

            for(int j=0;j<=n;j++)

            {

                for(int k=0;k<=m;k++)

                {

                    dp[i][j][k]=dp[i-1][j][k];//就是i位置不选的情况,去i-1位置选择

                    if(j>=g[i-1])//必须满足人数大于g[i],员工人数至少为n    

                        dp[i][j][k]+=dp[i-1][j-g[i-1]][max(0,k-p[i-1])];

                    dp[i][j][k]%=MOD;//防止超出int的值

                }

            }

        }

        return dp[len][n][m];

  

    }

};

下面是空间优化的代码

C++ 复制代码
class Solution

{

public:

    int profitableSchemes(int n, int m, vector<int>& g, vector<int>& p)

    {

        const int MOD=1e9+7;//取模的数

        int len=g.size();

        vector<vector<int>>dp(n+1,vector<int>(m+1));

  

        for(int j=0;j<=n;j++)dp[j][0]=1;//没有工作,没有利润的情况下,无论人数有多少都是空集的,只存在空集这一种情况的

  

        for(int i=1;i<=len;i++)

        {

            for(int j=n;j>=g[i-1];j--)

            {

                for(int k=m;k>=0;k--)

                {

                    dp[j][k]+=dp[j-g[i-1]][max(0,k-p[i-1])];

                    dp[j][k]%=MOD;//防止超出int的值

                }

            }

        }

        return dp[n][m];

    }

};
相关推荐
txzrxz21 分钟前
数论:排列数、组合数、费马小定理、逆元、同余定理
c++·算法·数论·组合数·费马小定理·逆元·排列数
xqqxqxxq24 分钟前
LeetCode Hot100 双指针专项题解笔记
笔记·算法·leetcode
闪电悠米1 小时前
力扣hot100-54.螺旋矩阵-模拟边界控制详解
算法·leetcode·矩阵
变量未定义~1 小时前
虚拟节点-星石传送阵(4星)、强连通分量
数据结构·算法
IT方大同1 小时前
C语言分支与循环语句
c语言·开发语言·算法
待磨的钝刨2 小时前
深入理解主成分分析(PCA)
人工智能·线性代数·算法·机器学习
geovindu3 小时前
java: Backtracking Algorithm
java·开发语言·windows·后端·算法·回溯算法
晚风叙码3 小时前
C++ vector底层模拟实现与迭代器失效深度剖析
c++·算法
To_OC11 小时前
LC 51 N 皇后:我以为难的是回溯,结果栽在了对角线下标
javascript·算法·leetcode