【Leetcode】474.一和零

474.一和零

题目

在计算机界中,我们总是追求用有限的资源获取最大的收益。

现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。

你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次。

 

示例 1:
输入: strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出: 4
解释: 总共 4 个字符串可以通过 5 个 0 和 3 个 1 拼出,即 "10","0001","1","0" 。

示例 2:
输入: strs = ["10", "0", "1"], m = 1, n = 1
输出: 2
解释: 你可以拼出 "10",但之后就没有剩余数字了。更好的选择是拼出 "0" 和 "1" 。

方法(动态规划)

1.状态定义(题目要什么就把什么定义为状态)

定义dp[i][j][k]为在有j个0和k个1的情况下,最多能拼出子数组strs[0…i]中的多少个字符串。

2.Base case

先确定dp[0][j][k],即能否用j个0和k个1拼出strs[0],若能则该dp值为1,若不能则为0.

3.状态转移方程

每到strs中的一个字符串时,都面临两个选择:拼这个字符串或者不拼这个字符串。dp值取二者中的较大值。

  • 若不拼这个字符串,$dp[i][j][k] = dp[i−1][j][k]$
  • 若拼这个字符串,假如当前的这个字符串有a个0,b个1。则拼这个字符串就会消耗这a个1,b个0。于是$dp[i][j][k] = dp[i-1][j-a][k-b] + 1$
  • 注意:当目前拥有的0的个数j和1的个数k小于拼这个字符串所需要的a和b时,只能选择不拼这个字符串。

最后返回$dp[strs.length - 1][m][n]$

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public int findMaxForm(String[] strs, int m, int n) {
int[][][] dp = new int[strs.length][m + 1][n + 1];
//设置base case
int[] countBase = count0And1(strs[0]);
for (int j = 0; j <= m; j++) {
for (int k = 0; k <= n; k++)
dp[0][j][k] = (j >= countBase[0] && k >= countBase[1]) ? 1 : 0;
}
for(int i = 1; i < strs.length; i++) {
int[] count = count0And1(strs[i]);
for (int j = 0; j <= m; j++) {
for (int k = 0; k <= n; k++) {
int a = count[0];
int b = count[1];
if(j < a || k < b)
dp[i][j][k] = dp[i-1][j][k];
else
dp[i][j][k] = Math.max(dp[i-1][j][k], dp[i-1][j-a][k-b]+1);
}
}
}
return dp[strs.length - 1][m][n];
}

//统计字符串str中0和1出现的次数
public int[] count0And1(String str){
int[] res = new int[2];
//res[0]代表字符串str中字符0出现的次数,res[1]代表字符串str中字符1出现的次数
for(char c: str.toCharArray()){
res[c - '0']++;
}
return res;
}

优化空间复杂度

由于dp数组的第i行只与它的前一行第i-1行有关,因此我们可以去掉i这个维度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m + 1][n + 1];
//设置base case
dp[0][0] = 0;
for(String str : strs){
int[] count = count0And1(str);
int a = count[0];
int b = count[1];
//注意:这里要逆向填充
for(int j = m; j >= a; j--) {
for(int k = n; k >= b; k--) {
dp[j][k] = Math.max(dp[j][k], dp[j-a][k-b]+1);
}
}
}
return dp[m][n];
}

//统计字符串str中0和1出现的次数
public int[] count0And1(String str){
int[] res = new int[2];
//res[0]代表字符串str中字符0出现的次数,res[1]代表字符串str中字符1出现的次数
for(char c: str.toCharArray()){
res[c - '0']++;
}
return res;
}