[算法手记] 动态规划 ,二维费用限制背包问题如何处理

前言: 本文将简单介绍一下背包问题的经典题型: 二维费用背包问题

一, 一和零

题目链接: https://leetcode.cn/problems/ones-and-zeroes/description/

1.题目解析

先来分析题意, 给定一个由0和1组成字符串数组, 找出最大的子集长度, 其中子集中选择的01 子串构成的集合中, 0 和1 的个数都要 不超过m 和 n

由于本题中要求返回符合的最大子集长度,子集无非就是对于一个元素的选和不选的产生的结果集,看到这一种选或者不选的问题,就要结合01背包了

如此一来就可以把题目转化为熟知的背包问题, 也就是在前 i 个物品中,挑选出不超过背包最大容量,但是又能满足最终收纳目标的问题

与其不同的是, 常见的背包问题, 只有一重条件限制, 那就是 收纳物品的体积 . 但是此题我们可以发现有两个限制条件, 也就是 字符串中 0和1 的个数, 也就是说 , 在装物品前 ,还需要统计字符串的01 构成个数

对于给定strs[ ] 中的第i个字符串,我们都有两种选择

  1. 不选当前的字符, 那直接继承上一轮的状态
  2. 选择当前的字符, 当前选择字符串中 的 0 1 个数需要累计,所以还需要在之前的状态中找到能容纳该字符串之后, 能 容纳j - a, k - b (a,b是字符串中01的个数) 的最大子集个数

以上就是这两种情况,但是还需要对其取max , 取max的核心是为了对于不选的情况会直接继承上一轮状态而不是强行填充

这句话如何理解呢? 举个例子,假如当前我们的背包中塞了 4个字符串, 面对第5个字符串时, 我们发现装不下它,

如果不选, 那么当前dp[i]的结果集长度为4,也就是继承状态 ,

但是如果选择的话, 这就需要在容量更小的情况下寻找最优的可能,并在此基础上+1 也就是面对新物品时,会进行分析,是继承好,还是腾出空间把它加进来好.这就是对以上两种情况取max的意义

本题由于每个dp表中 i 层的状态都依赖 i - 1 上一层的状态,所以直接使用滚动数组,从后往前遍历即可. 从后往前遍历的目的是为了防止一个字符被重复选择

dp表的初始化问题,在i = 0 时,也即是数组为空,此时无任何符合的子集,dp表全部默认为0 即可

2. 代码实现

java 复制代码
class Solution {

    public int findMaxForm(String[] strs, int m, int n) {

        int [][]dp = new int [m+1][n+1];

        for(String str : strs){

            int a = 0,b = 0;

            //统计 0 和1 的个数

            for(char ch : str.toCharArray()){

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

                else b++;

            }
			//滚动数组优化
            for(int j = m;j >= a;j--){

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

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

                }

            }

        }

        return dp[m][n];

    }

}

二, 盈利计划

题目链接:https://leetcode.cn/problems/profitable-schemes/description/

1.题目解析

本题要求找出符合要求最多计划数, 对于计划,有两种限制条件,人数和利润

人数不得超出n,对应人的利润和>=minprofit ,按照这种方式选择的才是合法的计划

看到这一类问题, 对于数组中遍历到的每个人,选和不选 , 并且存在条件限制(背包容量) ,我们可以使用背包思想来解决

由于每一轮的状态只依赖上一轮状态,同时限制因素有两个.直接开一个二维dp数组dp[j][k] ,dp [ j ] [ k ] : 选择的总人数<=j, 总利润>= k 的最大总计划数

dp表的初始化问题: 在没有利润限制, 利润为0 时, 无论怎么做,都存在一种符合要求那就是什么都不做, 不选择人来干活自然也是一种方案, 把二维dp表中首列全部初始化为1 即可

然后就是状态转移问题: 对于新的员工,选和不选

假设a 和b 是每一轮所耗费的人数和能获得的利润

1.不选 , 直接继承,dp[j][k]继承上一轮

2.选择, dp[j][k] 需要加上选择这个条件之后,剩余j - a 个员工情况下,满足剩余利润的方案,dp[j][k] += dp[j-a][k-b] , 难道这样就可以了吗,并非如此,对于当前的利润b如果非常大,只要选择了,k - b < 0 ,此时利润超标,既然做这项工作带来的利润已经远超当前目标 k,那么我们在扣除人数 a 后,对剩余利润的要求直接降为 0 即可。 只要加上"人数为 j-a 且获得利润为 0"时的所有方案数dp[j-a][0],就是当前做这项工作的全部可行方案。

其中j - a则需要限制不能为负,为什么?因为假如当前任务需要a个员工,但是我只有j个,j < a ,人手不够,这种情况不予考虑,我们让j从a开始倒序遍历即可

2.代码实现

java 复制代码
class Solution {

    public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {

        int[][]dp = new int[n+1][minProfit+1];

        int mod = (int)(1e9 + 7);

        for(int i = 0;i <= n;i++){//初始化,没有工作,利润>= 0 限制的方案数都为1

            dp[i][0] = 1;

        }

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

            int a = group[i-1];

            int b = profit[i-1];

            for(int j = n;j >= a;j--){

                for(int k  = minProfit;k >= 0;k--){

                    dp[j][k] += dp[j - a][Math.max(0,k - b)];

                    dp[j][k] %= mod;

                }

            }

        }

        return dp[n][minProfit];

    }

}

有关二维费用的背包问题的介绍就到这里了,如有纰漏还请指出~~

相关推荐
Chase_______1 小时前
LeetCode 1343 题解:定长滑动窗口经典入门题,从暴力枚举到高效优化一文搞懂
算法·leetcode·职场和发展
样例过了就是过了1 小时前
LeetCode热题100 单词拆分
c++·算法·leetcode·动态规划·哈希算法
王老师青少年编程1 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【跳跃与过河问题】:跳跳!
c++·算法·贪心·csp·信奥赛·跳跃与过河问题·跳跳
MediaTea1 小时前
ML:决策树的基本原理与实现
人工智能·算法·决策树·机器学习·数据挖掘
王老师青少年编程2 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【跳跃与过河问题】:独木桥
c++·算法·贪心·csp·信奥赛·跳跃与过河问题·独木桥
忡黑梨2 小时前
eNSP_DHCP配置
c语言·网络·c++·python·算法·网络安全·智能路由器
陈壮实的搬砖日记2 小时前
白话生成式推荐二:MiniOneRec之RQ-VAE
算法
陈壮实的搬砖日记2 小时前
白话生成式推荐二:MiniOneRec之SFT
算法
她说彩礼65万2 小时前
C语言 动态内存管理
c语言·开发语言·算法