474. 一和零

474. 一和零

题目链接

代码随想录

分析

这道题很明显也是 KM-46. 携带研究材料,经典的 01 背包问题是将物品放到背包中,物品有大小和价值两重属性,背包的容量大小有限,求能放下的物品的最大价值,这里,背包的限制只有大小,也就是说限制只有一个(一维的),但是这个题中,我们要选的物品(01 字符串)的只有一重属性,就是字符串本身,而背包的限制有两个,总的 0 的个数和总的 1 的个数,也就是说,限制是二维的,然后我们要求的值,是物品的个数。由于背包的限制是二维的,因此,没有别的选择,我们只能用一个三维的 dp 数组(一维遍历物品,一维遍历 0 的个数,一维遍历 1 的个数),

我们先以三维 dp 为例,然后再讲解二维

首先遍历数组的所有元素,将 0 的个数和 1 的个数记录到 count 中。

定义 dp 数组:dp[i][j][k] 的含义是,从索引 0 到索引 i 这些元素中,选择元素组成子集,子集中的所有元素满足 0 的个数最多为 j,1 的个数最多为 k,此时子集的最大子集的长度就是 dp[i][j][k]

KM-46. 携带研究材料 一样,本题只是背包变成了二维的,实际上的逻辑还是一样的,就是看这个子集包不包含物品 i:

最长的子集就是这两个数字中的最大值,因此状态转移方程就是 dp[i][j][k] = Math.max(dp[i-1][j][k],dp[i-1][j-count[i][0]][k-count[i][1]]+1)

然后就是初始化,

这个初始化有点复杂。

然后遍历顺序是,最外层遍历物品 i,里面两层遍历 0 的个数 j 和 1 的个数 k。总共三层遍历

当然,如果足够熟练,我们可以直接使用二维版本的(一维遍历 0 的个数,一维遍历 1 的个数)。

首先定义 dp 数组:dp[j][k] 的含义是,strs 中包含最多 j 个 0 和 k 个 1 的子集的最大长度。

然后状态转移方程就是 dp[j][k] = Math.max(dp[j][k],dp[j-count[i][0]][k-count[i][1]]+1)

初始化的时候,参考 KM-46. 携带研究材料#优化我们是从 i=0 的时候开始遍历,所以我们实际上是在没有选择任何物品的场景下初始化 dp 数组,而没有选择任何元素的情况下,dp[j][k] 始终为 0。

然后遍历顺序是,最外层遍历物品,第二层先倒序遍历 0 的个数 j ,第三层倒叙遍历 1 的个数 k。

一个小优化

将求 count 的 for 循环放到求 dp 数组中,可以节约一点性能。

解题

原始版本:

class Solution {

    public int findMaxForm(String[] strs, int m, int n) {
        int[][] count = new int[strs.length][2];
        for(int i=0;i<strs.length;i++){
            String str = strs[i];
            int num0= 0;
            int num1= 0;
            for(char c:str.toCharArray()){
                if('0'==c){
                    num0++;
                }
                if('1'==c){
                    num1++;
                }
            }
            int[] num ={num0,num1};
            count[i] = num;
        }

        // 开始构造dp数组
        int[][] dp = new int[m+1][n+1];
        for(int i=0;i<strs.length;i++){
            for(int j=m;j>=count[i][0];j--){
                for(int k=n;k>=count[i][1];k--){
                    dp[j][k] = Math.max(dp[j][k],dp[j-count[i][0]][k-count[i][1]]+1);
                }
            }
        }
        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];
}

相关题

KM-46. 携带研究材料

另一个求物品个数的题

322. 零钱兑换