力扣:474. 一和零(动态规划)(01背包)

题目:

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

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

如果 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 <= strsi.length <= 100
  • strsi 仅由 '0' 和'1' 组成
  • 1 <= m, n <= 100

思路:

本题是01背包问题

只不过这个背包有两个维度,一个是m 一个是n,而不同长度的字符串就是不同大小的待装物品。

动态规划五部曲:

  1. 确定dp数组(dp table)以及下标的含义

dpij最多有i个0和j个1的strs的最大子集的大小为dpij

  1. 确定递推公式

dpij 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。

dpij 就可以是 dpi - zeroNumj - oneNum + 1。

然后我们在遍历的过程中,取dpij的最大值。

所以递推公式:dpij = max(dpij, dpi - zeroNumj - oneNum + 1);

此时大家可以回想一下01背包的递推公式:dpj = max(dpj, dpj - weight\[i] + valuei);

对比一下就会发现,字符串的zeroNum和oneNum相当于物品的重量(weighti),字符串本身的个数相当于物品的价值(valuei)。

这就是一个典型的01背包! 只不过物品的重量有了两个维度而已。

python 复制代码
dp[i][j] = max(dp[i][j], dp[i - zero_num][j - one_num] + 1)
  1. dp数组如何初始化

01背包的dp数组初始化为0就可以。

因为物品价值不会是负数,初始为0,保证递推的时候dpij不会被初始值覆盖。

  1. 确定遍历顺序

01背包一定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历!

那么本题也是,物品就是strs里的字符串,背包容量就是题目描述中的m和n。

代码如下:

python 复制代码
            # 遍历m到zero_num,更新dp数组
            for i in range(m, zero_num - 1, -1):
                # 遍历n到one_num,更新dp数组
                for j in range(n, one_num - 1, -1):
                    # 更新dp[i][j]的值
                    dp[i][j] = max(dp[i][j], dp[i - zero_num][j - one_num] + 1)

m 和 n都是物品重量的一个维度,先遍历哪个都可以。

  1. 举例推导dp数组
    以输入:"10","0001","111001","1","0",m = 3,n = 3为例

最后dp数组的状态如下所示:

代码及详细注释:

python 复制代码
class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        # 创建一个二维数组dp,用于记录可以由前i个字符串组成的最大子集的个数
        dp = [[0] * (n + 1) for _ in range(m + 1)]
        # 遍历每个字符串
        for s in strs:
            zero_num = s.count('0')  # 统计0的个数
            one_num = s.count('1')  # 统计1的个数
            # 遍历m到zero_num,更新dp数组
            for i in range(m, zero_num - 1, -1):
                # 遍历n到one_num,更新dp数组
                for j in range(n, one_num - 1, -1):
                    # 更新dp[i][j]的值
                    dp[i][j] = max(dp[i][j], dp[i - zero_num][j - one_num] + 1)
        # 返回dp[m][n],表示可以由给定数量的0和1组成的最大子集的个数
        return dp[m][n]
  • 时间复杂度: O(kmn),k 为strs的长度
  • 空间复杂度: O(mn)
相关推荐
Jerry1 小时前
LeetCode 1002. 查找共用字符
算法
无限的鲜花1 小时前
协程本质是函数加状态机——零基础深入浅出 C++20 协程
c++·算法·c++20
旖-旎2 小时前
《LeetCode 64 最小路径和 || LeetCode 174 地下城游戏》
c++·算法·leetcode·动态规划
森林古猿12 小时前
再论斜率优化
c++·学习·算法
万法若空2 小时前
【数学-简单数论】同余中的逆
线性代数·算法
北极星日淘2 小时前
中古货品品相评级算法实战|Java权重计分实现标准化五级品相体系
开发语言·python
hangyuekejiGEO2 小时前
临沂GEO服务企业技术选型分析
人工智能·python
凯瑟琳.奥古斯特3 小时前
力扣1009补码解法C++实现
开发语言·c++·算法·leetcode·职场和发展
KaMeidebaby3 小时前
卡梅德生物技术快报|蛋白的叠氮基修饰:实操解析:核酸模板耦合蛋白的叠氮基修饰实现靶蛋白定点共价标记
前端·人工智能·物联网·算法·百度
闲猫4 小时前
Python 虚拟环境 virtualenv & uvicorn 服务搭建 & FAstAPI 使用
开发语言·python