持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
每日刷题 2022.06.05
- leetcode原题链接:leetcode.cn/problems/on…
- 难度:中等
- 方法:DP
题目
- 给你一个二进制字符串数组 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
解题思路
- 与
01背包的解题思路很像,主要在转换思路上 - 与
01背包不同的是,本题不能再是j - 1这样的写法,因为在本题中每个字符串都有自己独有的0和1的个数,因此再往回查找状态的时候,应该减去当前字符串strs[i]中的1和0的个数。 - 因此状态转移方程:
dp[i][j][z] = dp[i - 1][j][z] + dp[i - 1][j - lin][z - yi],其中lin表示strs[i]中的0的个数,yi表示strs[i]中的1的个数。 - 状态转移方程式含义:对于第
i个字符串,要求j个0和z个1的情况下,能够得到的最大子集的长度。 - 接下来就需要考虑初始化的边界问题:
- 既然不再是减1,假设对于当前
i = 1,我们不能再确定其是否会越界,如果此时strs[1]的字符串中有3个1,但是当前的j = 1,那么数组就会出现越界的问题。此时就可以考虑下,为什么会越界,是因为strs[1]中的1超过了我们要选择的1的个数。 - 因此不能选择
strs[1],dp[i][j][z] = dp[i - 1][j][z];
- 既然不再是减1,假设对于当前
- 初始化:
dp[0][0][z]和dp[0][j][0]这两种情况,都应该为0,因为此时没有可以选择的strs[i].
问题
- 为什么三维数组的三层长度,分别是
len + 1 \ m + 1 \ n + 1,因为对于每一个字符串都需要考虑到,而我们的状态转移方程式中当前的状态是依赖于前一个状态的,因此需要设置为len + 1。 m + 1: 是因为最终需要的是m个0和n个1,为了能取到m和n。
AC代码
/**
* @param {string[]} strs
* @param {number} m
* @param {number} n
* @return {number}
*/
var findMaxForm = function(strs, m, n) {
const len = strs.length;
// 创建一个三维数组
let dp = new Array(len + 1).fill(0).map(() => new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0)));
// 因为什么东西都没有,因此开始的0行全部赋为0
let yi = 0,lin = 0;
for(let i = 1; i <= len; i++) {
yi = 0,lin = 0;
// 统计当前的字符串中1和0的个数
let arr = strs[i - 1].split('');
arr.forEach(ele => {
if(ele == 1) yi++;
else if(ele == 0) lin++;
});
// dp
for(let j = 0; j <= m; j++) {
for(let z = 0; z <= n; z++) {
// m = 0,n = 1
// 不选的情况:当前字符串中的1大于要求的1的个数 或者 当前字符串中的0大于要求的0的个数
if(j < lin || z < yi) dp[i][j][z] = dp[i - 1][j][z];
// 选择:符合要求的0和1的个数
if(j - lin >= 0 && z - yi >= 0)
dp[i][j][z] = Math.max(dp[i - 1][j][z], dp[i - 1][j - lin][z - yi] + 1);
}
}
}
return dp[len][m][n];
};
优化后的代码
- 问题:为什么优化后,不需要写不选的情况了呢?
优化前:
优化后
- 去掉一层循环后,需要将循环的区间进行转换,当前所利用的数组就是上一层得到的结果,因此无需再考虑不选的情况
- 对比:那么没有优化的时候,为什么不能不考虑不选的情况呢?因为不选的时候,不加入不选的情况,那么下下一层的结果就会被影响。
AC代码
/**
* @param {string[]} strs
* @param {number} m
* @param {number} n
* @return {number}
*/
var findMaxForm = function(strs, m, n) {
const len = strs.length;
// 创建二维数组
let dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
for(let k = 0; k < len; k++) {
let lin = 0, yi = 0;
// 查找当前字符串中0和1的个数
strs[k].split('').forEach(ele => {
if(ele == 0) lin++;
else if(ele == 1) yi++;
});
// dp
for(let i = m; i >= lin; i--) {
for(let j = n; j >= yi; j--) {
dp[i][j] = Math.max(dp[i][j], dp[i - lin][j - yi] + 1);
}
}
}
return dp[m][n];
};