474. 一和零

474. 一和零

题目链接
代码随想录

分析

这道题很明显也是 背包问题-01背包,经典的 01 背包问题是将物品放到背包中,物品有大小和价值两重属性,背包的容量大小有限,求能放下的物品的最大价值,这里,背包的限制只有大小,也就是说限制只有一个(一维的),但是这个题中,我们要选的物品(01 字符串)的只有一重属性,就是字符串本身,而背包的限制有两个,总的 0 的个数和总的 1 的个数,也就是说,限制是二维的,然后我们要求的值,是物品的个数。由于背包的限制是二维的,因此,没有别的选择,我们只能用一个三维的 dp 数组(一维遍历物品,一维遍历 0 的个数,一维遍历 1 的个数),当然,如果足够熟练,我们可以直接使用二维版本的(一维遍历 0 的个数,一维遍历 1 的个数)。
以下我们就以二维版本的来分析:
首先定义 dp 数组:dp[j][k] 的含义是,strs 中包含最多 j 个 0 和 k 个 0 的子集的长度。
我们首先将物品 i 中包含的 0 的个数放到数组 mvalue 中,将其中包含的 1 的个数放到 nvalue 中。
然后我们确定状态转移方程,对于物品 i 来说,只有两种选择,就是把物品 i 放到背包里,和不把物品 i 放到背包里,如果不把物品 i 放到背包里,那么直接沿用从物品 0 到物品 i-1 的结果即可,即 dp[i-1][j][k],在二维版本里,就是 dp[j][k],如果放了物品 i,那么子集的长度就变成了 1(当前元素)加上 dp[i-1][j-mvalue[i]][k-nvalue[i]],在二维版本里,就是 1+dp[j-mvalue[i]][k-nvalue[i]]。最终,我们确定的状态转移方程是:dp[j][k] = Math.max(dp[j][k],1+dp[j-mvalue[i]][k-nvalue[i]])
初始化,dp[0][0]=0,其实也可以不这样初始化。
然后遍历顺序是,最外层遍历物品,里面两层遍历 0 的个数 j 和 1 的个数 k。总共三层遍历

一个小优化
我原本想的优化方向是如何不使用两个 for 循环来计算 dp(那样的话 dp 就不能是二维数组当前组合),然后我发现这道题只能这样做。
然后将求 mvalue 和 nvalue 的两个嵌套 for 循环放到求 dp 数组中,可以节约一点性能。

解题

原始版本:

public int findMaxForm(String[] strs, int m, int n) {
    // 将每一个元素的m和n求出来
    int[][] mvalue = new int[strs.length][1];
    int[][] nvalue = new int[strs.length][1];
    for (int i = 0; i < strs.length; i++) {
        char[] chars = strs[i].toCharArray();
        for (char c : chars) {
            if ('0' == c) {
                mvalue[i][0]++;
            } else if ('1' == c) {
                nvalue[i][0]++;
            }
        }
    }
    // 从三维dp,降到2维dp,因为背包,有两个条件
    // 最终我们要求的是 dp[m][n]
    int[][] dp = new int[m + 1][n + 1];
    // 核心推导过程依然是放与不妨物品i
    // 状态转移
    // dp[j][k] = Math.max(dp[j][k],1+dp[j-mvalue[i]][k-nvalue[i]])
    // 初始化
    dp[0][0] = 0;
    for (int i = 0; i < strs.length; i++) {
        for (int j = m; j >= mvalue[i][0]; j--) {
            for (int k = n; k >= nvalue[i][0]; k--) {
                dp[j][k] = Math.max(dp[j][k], 1 + dp[j - mvalue[i][0]][k - nvalue[i][0]]);
            }
        }
    }
    return dp[m][n];
}

优化一点儿的版本:

public int findMaxForm(String[] strs, int m, int n) {
    int[][] dp = new int[m + 1][n + 1];
    // 初始化
    dp[0][0] = 0;
    for (int i = 0; i < strs.length; i++) {
        int zeroCount = 0;
        int oneCount = 0;
        char[] chars = strs[i].toCharArray();
        for (char c : chars) {
            if ('0' == c) {
                zeroCount++;
            } else if ('1' == c) {
                oneCount++;
            }
        }
        for (int j = m; j >= zeroCount; j--) {
            for (int k = n; k >= oneCount; k--) {
                dp[j][k] = Math.max(dp[j][k], 1 + dp[j - zeroCount][k - oneCount]);
            }
        }
    }
    return dp[m][n];
}

相关题

背包问题-01背包
另一个求物品个数的题
322. 零钱兑换