【算法挨揍日记】day45——474. 一和零、879. 盈利计划

2024-01-03 15:52

本文主要是介绍【算法挨揍日记】day45——474. 一和零、879. 盈利计划,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 474. 一和零

474. 一和零

题目描述:

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

请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。

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

 解题思路:

算法思路:
先将问题转化成我们熟悉的题型。
i. 在⼀些物品中「挑选」⼀些出来,然后在满⾜某个「限定条件」下,解决⼀些问题,⼤概率
是背包模型;
ii. 由于每⼀个物品都只有 1 个,因此是⼀个「01 背包问题」。
但是,我们发现这⼀道题⾥⾯有「两个限制条件」。因此是⼀个「⼆维费⽤的 01 背包问题」。那
么我们定义状态表⽰的时候,来⼀个三维 dp 表,把第⼆个限制条件加上即可。
1. 状态表⽰:
dp[i][j][k] 表⽰:从前 i 个字符串中挑选,字符 0 的个数不超过 j ,字符 1 的个数不
超过 k ,所有的选法中,最⼤的⻓度。
2. 状态转移⽅程:
线性 dp 状态转移⽅程分析⽅式,⼀般都是「根据最后⼀步」的状况,来分情况讨论。为了⽅便
叙述,我们记第 i 个字符中,字符 0 的个数为 a ,字符 1 的个数为 b
i. 不选第 i 个字符串:相当于就是去前 i - 1 个字符串中挑选,并且字符 0 的个数不超
j ,字符 1 的个数不超过 k 。此时的最⼤⻓度为 dp[i][j][k] = dp[i - 1]
[j][k]
ii. 选择第 i 个字符串:那么接下来我仅需在前 i - 1 个字符串⾥⾯,挑选出来字符 0
个数不超过 j - a ,字符 1 的个数不超过 k - b 的最⻓⻓度,然后在这个⻓度后⾯加
上字符串 i 即可。。此时 dp[i][j][k] = dp[i - 1][j - a][k - b] + 1
但是这种状态不⼀定存在,因此需要特判⼀下。
综上,状态转移⽅程为: dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - a]
[k - b] + 1)
3. 初始化:
当没有字符串的时候,没有⻓度,因此初始化为 0 即可。
4. 填表顺序:
保证第⼀维的循环「从⼩到⼤」即可。
5. 返回值:
根据「状态表⽰」,我们返回 dp[len][m][n]
其中 len 表⽰字符串数组的⻓度。
6. 空间优化:
所有的「背包问题」,都可以进⾏空间上的优化。
对于「⼆维费⽤的 01 背包」类型的,我们的优化策略是:
i. 删掉第⼀维;
ii. 修改第⼆层以及第三层循环的遍历顺序即可

解题代码:

class Solution {
public:int f(string s,char ch){int ret=0;for(int i=0;i<=s.size();i++)if(s[i]==ch)    ret++;return ret;}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++){for(int j=0;j<=m;j++){for(int k=0;k<=n;k++){dp[i][j][k]=dp[i-1][j][k];int a=f(strs[i-1],'0');//0的个数int b=f(strs[i-1],'1');//1的个数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];}
};

 879. 盈利计划

879. 盈利计划

题目描述:

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

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

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

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

 

解题思路:

算法思路:
这道题⽬⾮常难读懂,但是如果结合例⼦多读⼏遍,你就会发现是⼀个经典的「⼆维费⽤的背包问
题」。因此我们可以仿照「⼆维费⽤的背包」来定义状态表⽰。
1. 状态表⽰:
dp[i][j][k] 表⽰:从前 i 个计划中挑选,总⼈数不超过 j ,总利润⾄少为 k ,⼀共有多
少种选法。
注意注意注意,这道题⾥⾯出现了⼀个「⾄少」,和我们之前做过的背包问题不⼀样。因此,我们
在分析「状态转移⽅程」的时候要结合实际情况考虑⼀下。
2. 状态转移⽅程:
⽼规矩,根据「最后⼀个位置」的元素,结合题⽬的要求,我们有「选择」最后⼀个元素或者「不
选择」最后⼀个元素两种策略:
i. 不选 i 位置的计划:那我们只能去前 i - 1 个计划中挑选,总⼈数不超过 j ,总利润
⾄少为 k 。此时⼀共有 dp[i - 1][j][k] 种选法;
ii. 选择 i 位置的计划:那我们在前 i - 1 个计划中挑选的时候,限制就变成了,总⼈数不
超过 j - g[i] ,总利润⾄少为 k - p[i] 。此时⼀共有 dp[i - 1][j - g[i]]
[k - p[i]]
第⼆种情况下有两个细节需要注意:
1. j - g[i] < 0 :此时说明 g[i] 过⼤,也就是⼈数过多。因为我们的状态表⽰要
求⼈数是不能超过 j 的,因此这个状态是不合法的,需要舍去。
2. k - p[i] < 0 :此时说明 p[i] 过⼤,也就是利润太⾼。但是利润⾼,不正是我
们想要的嘛?所以这个状态「不能舍去」。但是问题来了,我们的 dp 表是没有负数的
下标的,这就意味着这些状态我们⽆法表⽰。其实,根本不需要负的下标,我们根据实
际情况来看,如果这个任务的利润已经能够达标了,我们仅需在之前的任务中,挑选出
来的利润⾄少为 0 就可以了。因为实际情况不允许我们是负利润,那么负利润就等价
于利润⾄少为 0 的情况。所以说这种情况就等价于 dp[i][j][0] ,我们可以对 k
- p[i] 的结果与 0 取⼀个 max
综上,我们的状态转移⽅程为:
dp[i][j][k] = dp[i - 1][j][k] + dp[i - 1][j - g[i - 1]][max(0, k
- p[i - 1])]
3. 初始化:
当没有任务的时候,我们的利润为 0 ,此时⽆论⼈数限制为多少,我们都能找到⼀个「空集」的
⽅案。
因此初始化 dp[0][j][0] 的位置为 1 ,其中 0 <= j <= n
4. 填表顺序:
根据「状态转移⽅程」,我们保证 i 从⼩到⼤即可。
5. 返回值:
根据「状态表⽰」,我们返回 dp[len][m][n]
其中 len 表⽰字符串数组的⻓度。
6. 空间优化:
所有的「背包问题」,都可以进⾏空间上的优化。
对于「⼆维费⽤的 01 背包」类型的,我们的优化策略是:
i. 删掉第⼀维;
ii. 修改第⼆层以及第三层循环的遍历顺序即可。

解题代码:

class Solution {
public:int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) {const int MOD=1e9+7;int len=group.size();vector<vector<vector<int>>>dp(len+1,vector<vector<int>>(n+1,vector<int>(minProfit+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<=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(k-profit[i-1],0)];dp[i][j][k]%=MOD;}}}return dp[len][n][minProfit];}
};

这篇关于【算法挨揍日记】day45——474. 一和零、879. 盈利计划的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/566240

相关文章

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

Oracle数据库执行计划的查看与分析技巧

《Oracle数据库执行计划的查看与分析技巧》在Oracle数据库中,执行计划能够帮助我们深入了解SQL语句在数据库内部的执行细节,进而优化查询性能、提升系统效率,执行计划是Oracle数据库优化器为... 目录一、什么是执行计划二、查看执行计划的方法(一)使用 EXPLAIN PLAN 命令(二)通过 S