「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战」
题目
难度:中等
给你一个二进制字符串数组 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 <= 6001 <= strs[i].length <= 100strs[i]仅由'0'和'1'组成1 <= m, n <= 100
解题思路
设dp[i][j][k]表示前i个字符串构成的数组中,最多有j个0和k个1的最大子集的大小
其中0 <= i <= strs.length; 0 <= j <= m; 0 <= k <= n, 规定i = 0时,最大子集大小均为0
对于第i个字符串(从1开始计数),设0的数量为zeros,1的数量为ones
- 若
zeros > j或者ones > k,即该字符串本身不符合条件 故不选该字符串,于是:dp[i][j][k] = dp[i - 1][j][k],即最大子集大小与前i - 1个字符串的最大子集大小相同 - 若
zeros <= j并且ones <= k,即该字符串可以被选入子集- 若不选该字符串,则
dp[i][j][k] = dp[i - 1][j][k] - 若选取该字符串,则
dp[i][j][k] = dp[i - 1][j - count[0]][k - count[1]] + 1,即去掉该字符串占据的“空间”以后剩余“空间”能达成的最大子集大小 + 1dp[i][j][k]的值取两种情况的较大者 最终满足条件的结果为dp[strs.length][m][n]
- 若不选该字符串,则
代码
/**
* @param {string[]} strs
* @param {number} m
* @param {number} n
* @return {number}
*/
var findMaxForm = function(strs, m, n) {
const len = strs.length
const dp = new Array(len + 1).fill(0).map(() => new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0)))
for (let i = 1; i <= len; i++) {
let count = countZerosAndOnes(strs[i - 1])
for (let j = 0; j <= m; j++) {
for (let k = 0; k <= n; k++) {
if (j < count[0] || k < count[1]) {
dp[i][j][k] = dp[i - 1][j][k]
} else {
dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - count[0]][k - count[1]] + 1)
}
}
}
}
return dp[len][m][n]
};
var countZerosAndOnes = function(str) {
const arr = new Array(2).fill(0)
for (let c of str) {
arr[c]++
}
return arr
}
复杂度分析
- 时间复杂度:O(lmn + L),l为字符串数组长度,L为所有字符串的总长度
- 空间复杂度:O(lmn),需要创建一个三维数组dp
优化
因为dp[i][j][k]只与dp[i - 1][][]有关,从而无需创建三维数组,可以使用滚动数组的方式,去掉三维数组的第一个维度
实现时采用倒序从后向前覆盖,因为后面的元素计算会依赖前面的元素
dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - count[0]][k - count[1]] + 1)
所以从后向前覆盖保证前面的元素是dp[i - 1][][]转移来的
/**
* @param {string[]} strs
* @param {number} m
* @param {number} n
* @return {number}
*/
var findMaxForm = function(strs, m, n) {
const len = strs.length
const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0))
let pre
for (let i = 1; i <= len; i++) {
let count = countZerosAndOnes(strs[i - 1])
for (let j = m; j >= count[0]; j--) {
for (let k = n; k >= count[1]; k--) {
dp[j][k] = Math.max(dp[j][k], dp[j - count[0]][k - count[1]] + 1)
}
}
}
return dp[m][n]
};
var countZerosAndOnes = function(str) {
const arr = new Array(2).fill(0)
for (let c of str) {
arr[c]++
}
return arr
}
复杂度分析
- 时间复杂度:O(lmn + L),l为字符串数组长度,L为所有字符串的总长度
- 空间复杂度:O(mn),需要创建一个二维数组dp