LeetCode——474. 一和零

266 阅读2分钟

「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

题目

难度:中等

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

请你找出并返回 strs 的最大子集的长度,该子集中 最多m0n1

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

示例 1:

输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5031 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 41 ,大于 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

解题思路

dp[i][j][k]表示前i个字符串构成的数组中,最多有j0k1的最大子集的大小 其中0 <= i <= strs.length; 0 <= j <= m; 0 <= k <= n, 规定i = 0时,最大子集大小均为0 对于第i个字符串(从1开始计数),设0的数量为zeros1的数量为ones

  1. zeros > j或者ones > k,即该字符串本身不符合条件 故不选该字符串,于是:dp[i][j][k] = dp[i - 1][j][k],即最大子集大小与前i - 1个字符串的最大子集大小相同
  2. zeros <= j并且ones <= k,即该字符串可以被选入子集
    1. 若不选该字符串,则dp[i][j][k] = dp[i - 1][j][k]
    2. 若选取该字符串,则dp[i][j][k] = dp[i - 1][j - count[0]][k - count[1]] + 1,即去掉该字符串占据的“空间”以后剩余“空间”能达成的最大子集大小 + 1 dp[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
}

复杂度分析

  1. 时间复杂度:O(lmn + L),l为字符串数组长度,L为所有字符串的总长度
  2. 空间复杂度: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
}

复杂度分析

  1. 时间复杂度:O(lmn + L),l为字符串数组长度,L为所有字符串的总长度
  2. 空间复杂度:O(mn),需要创建一个二维数组dp