题目
给你一个二进制字符串数组 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ones-and-zeroes
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
想法
不得不说,遇到这道题,又是不会做,怎么这么难嘞。
还是那五步骤:
1.dp数组的定义和下标。
2.递推公式。
3.dp数组如何初始化,初始化也需要注意。
4.遍历顺序,比较考究,01 先遍历背包,后遍历物品。
4.1排列和组合的遍历顺序是不相同的。
4.1.1 排列:背包在外 物品在内。
4.1.2 组合:物品在外,背包在内。
5.(出现问题)打印dp数组。(打印dp数组,检查是否有问题,检验1 2 3 4 步骤)
这道题我也不会,就看了评论,评论和官方解答有用二维的,也有用三维的,三维的居多,我不会,所以先看了二维的。二维的也按照上面的顺序分析一下什么含义。
- dp[i][j]就是有i个0和j个1时候的最大子集。
- dp[i][j] = Math.max(dp[i][j],dp[i-zeronum][j-onenum]+1);本身或者减去所消耗的0和1的次数。
- 初始化 那就是dp[0][0]=0;
- 考察的是集合,相当于组合,那应该是物品在外,背包在内。
大致想法,先循环物品,记录下0和1的个数, 再循环背包,然后遍历每个字符串中所含有的0 和1的个数,进行递推公式。
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m+1][n+1];
dp[0][0] = 0 ;
//遍历 物品 string
for(String str:strs)
{
//记录每个str的0和1的个数
int zeronum = 0,onenum = 0;
for(char c:str.toCharArray())
{
if(c=='0') zeronum++;
else onenum++;
}
//开始进行动态方程
//这里动态方程自上而下 数组初始化时候都是0
for(int i = m ; i >=zeronum;i--)
{
for(int j = n ; j >=onenum;j--)
{
dp[i][j] = Math.max(dp[i][j],1+dp[i-zeronum][j-onenum]);
}
}
}
return dp[m][n];
}
}
在写的时候,要想到之前学动态规划时候的一张数组表,这个数组表印在脑子里,这样才能知道动态规划的递归方程怎么来的。我根据评论中的构建一个二维的数组表,开始的时候初始化其实都是dp[i][j] =0;但是循环一次 就会+1,最后,就成为最大子集。
其实吧,这两个for循环那里 我还是不太好懂。自己画了一个二维表,第一次的时候,大部分都是0+1等于1,横坐标代表0的个数,当你0消耗了,那就减去0的个数,1消耗了就减去1的个数。
官方答案 三维数组
通常背包就只有一种容量,但是这道题有两个容量 分别是0 和1;二维的时候是物品和容量,三维的是字符串,0容量1 容量。那就需要定义三维数组d[i][j][k],表示在第i个个字符串中,j个0和k个1的字符串数量。
初始化应该是dp[0][0][0]=0;
边界应该是 l m n ,l是字符串的长度。
当j k 小于0 和1 的数量 不选第i个字符串的时候就是dp[i-1][j][k];
当j k 大于0 1 的 数量 不选第i个字符串的时候就是dp[i-1][j][k];
如果选的话,就是dp[i][j-zeronum][k-onenum];
还有在注意一下for循环的顺序就好。
接下来写一下代码:
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int[][][] dp = new int[strs.length+1][m+1][n+1];
dp[0][0][0] = 0 ;
for(int i = 1 ;i<=strs.length;i++)
{
int zeronum = 0 ,onenum = 0 ;
for(char c: strs[i-1].toCharArray())
{
if(c == '0') zeronum++;
else onenum++;
}
for(int j = 0 ; j<=m;j++)
{
for(int k = 0; k<=n;k++)
{
dp[i][j][k] = dp[i-1][j][k];
if(j>=zeronum&&k>=onenum)
{
dp[i][j][k] = Math.max(dp[i-1][j][k],dp[i-1][j-zeronum][k-onenum]+1);
}
}
}
}
return dp[strs.length][m][n];
}
}
总结
写完二维的再去写三维的,忽然感觉自己够写出代码了,其实嗄,在面对01背包问题,经常回去看答案,现在要能培养出自己不看答案独立思考的能力才行啊,从那五步入手,dp[i][j]的定义,初始化,for循环的内外顺序,动态方程怎么写,每次做题时候这些需要考虑到,在本题当中,那个动态方程需要进行判断,如果想不到,那也做不出来,还有就是考虑边界问题等等。