动态规划进阶06:⼀和零

83 阅读1分钟

⼀和零

力扣:474. 一和零 - 力扣(LeetCode)
给你一个二进制字符串数组 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 <= 600
1 <= strs[i].length <= 100
strs[i] 仅由 '0' 和 '1' 组成
1 <= m, n <= 100
注意:本题中strs 数组⾥的元素就是物品,每个物品都是⼀个!⽽m 和 n相当于是⼀个背包,两个维度的背包。
所以:但本题其实是01背包问题!
这不过这个背包有两个维度,⼀个是m ⼀个是n,⽽不同⻓度的字符串就是不同⼤⼩的待装物品。只不过这里的物品得继续分为'0'和'1' 的个数。

动规五部曲:

1. 确定dp数组以及下标的含义

dp[i][j]:最多有i个0和j个1的strs的最⼤⼦集的⼤⼩为dp[i][j]。

2. 确定递推公式

dp[i][j] 可以由前⼀个strs⾥的字符串推导出来,strs⾥的字符串有zeroNum个0,oneNum个1。
dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。
然后我们在遍历的过程中,取dp[i][j]的最⼤值。
所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
此时⼤家可以回想⼀下01背包的递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);对⽐⼀下就会发现,字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])。

3. dp数组如何初始化

01背包的dp数组初始化为0就可以。因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。

4. 确定遍历顺序

在之前的文章中我们每次都讲01背包为什么⼀定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历!那么本题也是,物品就是strs⾥的字符串,背包容量就是题⽬描述中的m和n。
Java代码如下:

 for(String a : strs){// 遍历字符确定'0','1'的个数
            int get0 = 0;
            int get1 = 0;
            for(int i = 0; i < a.length(); i++)
                if(a.charAt(i) == '0')
                    get0++;
                else
                    get1++;
            for(int x = m; x >= get0; x--)遍历背包从后往前
                for(int y = n; y >= get1; y--)
                    dp[x][y] = Math.max(dp[x][y], dp[x - get0][y - get1] + 1);
        }

5. 举例推导dp数组

以输⼊:["10","0001","111001","1","0"],m = 3,n = 3为例最后dp数组的状态如下所示:

image.png
如果对结果不理解,回顾一下递推公式和遍历顺序 以上分析完毕,Java代码如下:

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m + 1][n + 1];
        for(String a : strs){
            int get0 = 0;
            int get1 = 0;
            for(int i = 0; i < a.length(); i++)
                if(a.charAt(i) == '0')
                    get0++;
                else
                    get1++;
            for(int x = m; x >= get0; x--)
                for(int y = n; y >= get1; y--)
                    dp[x][y] = Math.max(dp[x][y], dp[x - get0][y - get1] + 1);
        }
        return dp[m][n]
    }
}