携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第32天,点击查看活动详情
题目描述
给你一个二进制字符串数组 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,里面元素均只包含为'0'或'1';给定m代表子集中最大的'0'的个数;给定n代表子集中最大的'1'的个数;返回最大满足条件的子集长度;
要求能够组成的最长的子集,这也是个最优子结构的问题。最优子结构一般都是通过回溯算法得到的。那这道题可以先看看怎么进行回溯。每一次选择一个字符串然后再在剩下的字符串中做选择;依次类推,最终找到满足条件的最长的子串。
但是在类推过程中可以看出来,其实在回溯的过程中有重叠的部分。所以可以用动态规划来进行解决;
假定数组strs中的字符串个数为l;
首先确定动态规划的状态,这里因为条件中有2个元素,所以动态规划应该是三层dp[i][j][k]代表在strs的前i个字符串中,满足'0'的个数为j,‘1’的个数为k条件的最大子串长度,所以我们要求的当i=l;j=m;k=n时即为答案;
转移方程
-
当当前strs[i]字符串中包含的'0','1'其中一个的数量大于j、k时,则当前字符串不能进入选择,即它的值只取决于它前一个字符串的最大值;此时dp[i][j][k] = dp[i-1][j][k];
-
当当前strs[i]字符串中包含的'0','1'设定为a、b,a、b的值均不大于j或k时,当前子串有两种选择:
-
选择当前字符串,dp[i][j][k] = dp[i-1][j-a][k-b]+1;
-
不选择当前字符串,dp[i][j][k] = dp[i-1][j][k];
因为这里求的是最大长度,所以要求这两种方式的最大值,即dp[i][j][k] = Math.max(dp[i-1][j-a][k-b]+1,dp[i-1][j][k]);
-
边界条件
当strs的长度为0时,可选择的子串也为0,即dp[0][j][k]=0;
代码实现
public static int findMaxForm(String[] strs, int m, int n) {
// 创建dp table
int l = strs.length;
// int数组默认值为0
int[][][] dpTable = new int[l+1][m+1][n+1];
for (int i = 1 ; i <= l; i ++) {
for (int j = 0; j <= m ; j ++) {
for (int k = 0 ; k <= n ; k ++) {
// 获取字符串中0,1的数量
int[] nums = getNum(strs[i - 1]);
int a = nums[0];
int b = nums[1];
if (a > j|| b > k) {
dpTable[i][j][k] = dpTable[i-1][j][k];
} else {
dpTable[i][j][k] = Math.max(dpTable[i-1][j-a][k-b]+1,dpTable[i-1][j][k]);
}
}
}
}
return dpTable[l][m][n];
}
private static int[] getNum(String str) {
int[] num = new int[2];
for (int i=0; i < str.length() ; i ++) {
num[str.charAt(i)-'0']++;
}
return num;
}