474. 一和零
题目描述
给你一个二进制字符串数组 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 <= strs[i].length <= 100
- strs[i] 仅由 ‘0’ 和 ‘1’ 组成
- 1 <= m, n <= 100
动态规划
这段代码是解决力扣题目474「一和零」的一个动态规划解法。题目要求在给定一组二进制字符串和两个整数限制m(最多0的数量)和n(最多1的数量)的情况下,找出最大的满足条件的字符串子集。下面是对这段代码的详细注解:
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
// 初始化动态规划数组dp,dp[i][j]表示最多有i个0和j个1时能组成的最大子集的长度
vector<vector<int>> dp(m+1,vector<int>(n+1,0));
// 遍历字符串数组strs中的每一个字符串
for(string s : strs) {
int x=0,y=0; // x和y分别用来统计字符串中0的数量和1的数量
// 统计当前字符串中0和1的数量
for(int i=0; i<s.size(); i++)
if(s[i]=='0') x++;
else y++;
// 遍历所有可能的0和1的组合数量,从m和n开始逆序遍历,确保每个字符串只被考虑一次
for(int i=m; i>=x; i--) {
for(int j=n; j>=y; j--) {
// 更新dp[i][j],为当前最大子集的长度,和使用当前字符串后的最大子集长度之间的较大值
// dp[i-x][j-y]+1 表示在当前字符串加入子集前,已有的最大子集的长度,再加上当前字符串
dp[i][j] = max(dp[i][j], dp[i-x][j-y]+1);
}
}
}
// 返回满足条件的最大子集的长度
return dp[m][n];
}
};
这段代码使用了二维动态规划的方法,dp[i][j]
表示在最多含有i
个0和j
个1的情况下,能组成的最大子集的长度。初始化时,dp
数组的所有元素都设置为0。
通过两层循环遍历strs
数组中的每个字符串s
,首先计算出s
中0和1的数量x
和y
。然后,通过另外两层逆序遍历(从m
到x
,从n
到y
)更新dp
数组。逆序遍历是为了确保在更新dp[i][j]
时使用的是未加入当前字符串s
时的最大子集长度,防止同一个字符串被重复计算。
在内层循环中,dp[i][j] = max(dp[i][j], dp[i-x][j-y]+1);
这行代码是核心,它比较了不加入当前字符串前的最大子集长度和加入当前字符串后的最大子集长度,取两者的较大值作为新的dp[i][j]
。
最后,dp[m][n]
就是在最多有m
个0和n
个1的限制下,能组成的最大子集的长度,也就是题目所求的答案。