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

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背包的话

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

二维费用

又多了一个限制条件

dp[i] [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背包而不是完全背包

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

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

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

返回值

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<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];

    }

};
相关推荐
修己xj4 分钟前
探索设计模式的宝库:Java-Design-Patterns
算法
鲨鱼辣椒_TUT7 分钟前
MySQL连接算法和小表驱动大表的原理
mysql·算法·adb
设计师小聂!21 分钟前
力扣热题100------21.合并两个有序链表
算法·leetcode·链表
এ᭄画画的北北1 小时前
力扣-1.两数之和
数据结构·算法·leetcode
shenghaide_jiahu2 小时前
数学建模——递归和动态规划
算法·数学建模·动态规划
黑色的山岗在沉睡2 小时前
P1948 [USACO08JAN] Telephone Lines S
数据结构·c++·算法·图论
快去睡觉~3 小时前
力扣301:删除无效的括号
数据结构·算法·leetcode
技术炼丹人4 小时前
从RNN为什么长依赖遗忘到注意力机制的解决方案以及并行
人工智能·python·算法
NfN-sh5 小时前
计数组合学7.12( RSK算法的一些推论)
笔记·学习·算法