474. 一和零 (ones and zeroes)

3,839 阅读2分钟

"你的背包,让我不再走得缓慢"

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情

474. 一和零 题目描述:给你一个二进制字符串数组 strsstrs 和两个整数 mmnn。请你找出并返回 strsstrs最大子集 的长度,该子集中最多有 mm00nn11 。如果 xx 的所有元素也是 yy 的元素,集合 xx 是集合 yy 的子集 。

示例
输入: strs=["10","0001","111001","1","0"],m=5,n=3strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出: 44
解释: 最多有 55003311 的最大子集是 "10","0001","1","0"{"10","0001","1","0"},因此答案是 44 。其他满足题意但较小的子集包括 "0001","1"{"0001","1"}"10","1","0"{"10","1","0"}"111001"{"111001"} 不满足题意,因为它含 4411 ,大于 nn 的值 33

中规中矩的动态规划

1、定义 dp 状态数组

典型 0-1 背包 问题:定义 strsstrs 数组在 [0,i][0,i] 索引区间上,jj00kk11 限制下的子集的最大长度为 dp[i][j][k]dp[i][j][k],其中 i[0,len)i\in[0,len)len=strs.lengthlen=strs.lengthj[0,m]j\in[0,m]k[0,n]k\in[0,n]

2、定义 dp 状态方程

确定当前的 strs[i]strs[i]zeroszeros00onesones11

  • 如果背包的容量不允许更多的 0011,即 j<zerosj<zeros 或! k<onesk<ones,或者主动舍弃 strs[i]strs[i],此时有 dp[i][j][k]=dp[i1][j][k]dp[i][j][k] = dp[i-1][j][k]

  • 如果背包的容量即允许 zeroszeros00onesones11,此时 jzerosj \ge zeroskonesk \ge ones,此时有 dp[i][j][k]=dp[i1][jzeros][kones]+1dp[i][j][k]=dp[i−1][j−zeros][k−ones]+1

3、确定 dp 初始状态

dp[i][0][0]dp[i][0][0] 代表从 [0,i][0,i] 中选择元素,00000011 限制下的子集最大长度,故! dp[i][0][0]dp[i][0][0] 恒为 00

题意中规定:strs[i]strs[i] 的长度至少为 11,且 strs[i]strs[i] 至少由 11001100 组成,所以如果背包的二维条件都限制为 00,那子集的最大长度恒为 00;如果没有“ strs[i]strs[i] 至少由 11001111 组成”的限制条件,这里要计算 strs[i]strs[i]zeroszeros00onesones11,如果 zeros==0zeros==0 ones==0ones==0,子集的最大长度为 11,否则为 11。)

dp[0][j][k]dp[0][j][k] 代表从 00 中选择元素(只能选择 strs[0]strs[0]),jj00kk11 限制下的子集最大长度。这里先要计算 strs[0]strs[0]zeroszeros00onesones11,如果 zerosjzeros \le j oneskones \le k,子集的最大长度为 11,否则为 00

4、确定遍历顺序

  • 第一层遍历从 i=1i=1i=len1i=len - 1

  • 第二层遍历从 j=1j=1j=mj=m

  • 第三层遍历从 k=1k=1k=nk=n

5、确定最终返回值

回归 dpdp 定义,即返回 dp[len1][m][n]dp[len-1][m][n]

6、代码示例

/**
 * 空间复杂度 O(l*m*n),其中l是strs数组的长度
 * 时间复杂度 O(l*m*n+L),其中L是strs数组所有字符串的长度和
 */
function findMaxForm(strs: string[], m: number, n: number): number {
    const len = strs.length;
    const dp = Array.from({ length: len }, 
    () => new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0)));

    for(let i = 0; i < len; i++) {
        dp[i][0][0] = 0; // 初始化时已经是0了,为了理解方便,再赋值一遍
    }

    const { zeros, ones } = findZerosAndOnes(strs[0]);
    for (let j = 0; j <= m; j++) {
        for (let k = 0; k <= n; k++) {
            dp[0][j][k] = (zeros <= j && ones <= k) ? 1 : 0;
        }
    }

    for (let i = 1; i < len; i++) {
        const { zeros, ones } = findZerosAndOnes(strs[i]);

        for (let j = 0; j <= m; j++) {
            for (let k = 0; k <= n; k++) {
                if (j >= zeros && k >= ones) {
                    dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - zeros][k - ones] + 1);
                } else {
                    dp[i][j][k] = dp[i - 1][j][k];
                }
            }
        }
    }

    return dp[len - 1][m][n];
};

function findZerosAndOnes(str: string): { zeros: number, ones: number } {
    let zeros = 0;
    let ones = 0;

    for(let i = 0, len = str.length; i < len; i++) {
        if (str[i] === '0') {
            zeros += 1;
        } else if (str[i] === '1') {
            ones += 1;
        }
    }

    return { zeros, ones };
}

参考

# 重识背包问题(上)

# 重识背包问题(下)