首先,统计出每一个字符串有多少的0和1分别放到
a
0
[
]
和
a
1
[
]
a0[]和a1[]
a0[]和a1[]数组中去,这样的话,这个0和1的个数就相当于背包问题中的背包的容量,这不过这里有两个容量,每一个字符串的价值为1,这样我们就转化成了背包问题。
状态转移方程:
d
p
[
k
]
[
i
]
[
j
]
dp[k][i][j]
dp[k][i][j]代表前k件物品放入0的数目最多为i,1的数目最多为j的背包中,我们可以得到的最大的字符串的个数。
d
p
[
k
]
[
i
]
[
j
]
=
m
a
x
(
d
p
[
k
−
1
]
[
i
−
a
0
[
k
]
]
[
j
−
a
1
[
k
]
]
,
d
p
[
k
−
1
]
[
i
]
[
j
]
)
,
k
>
=
1
,
i
>
=
a
0
[
k
]
,
k
>
=
a
1
[
k
]
dp[k][i][j]=max(dp[k-1][i-a0[k]][j-a1[k]],dp[k-1][i][j]),k>=1,i>=a0[k],k>=a1[k]
dp[k][i][j]=max(dp[k−1][i−a0[k]][j−a1[k]],dp[k−1][i][j]),k>=1,i>=a0[k],k>=a1[k],max的第一个参数代表选择第i个字符串,第二个参数代表不选择第i个字符串。
时间复杂度为
O
(
m
n
l
)
=
600
∗
100
∗
100
=
1
0
6
O(mnl)=600*100*100=10^6
O(mnl)=600∗100∗100=106可以承受。
空间复杂度
O
(
m
n
l
)
=
600
∗
100
∗
100
∗
4
/
2
20
约
等
于
46
M
O(mnl)=600*100*100*4/2^{20}约等于46M
O(mnl)=600∗100∗100∗4/220约等于46M,也可以承受。
但是空间可以压缩,只要我们稍微更改一下循环的顺序即可,和01背包的压缩思想是一致的。
细节,虽然上面的状态转移方程写的
k
>
=
1
k>=1
k>=1,但是循环的时候我们可以从0开始写,考虑边界条件,
d
p
[
0
]
[
i
]
[
j
]
=
0
dp[0][i][j]=0
dp[0][i][j]=0,i和j的循环需要覆盖到范围
0
−
m
和
0
−
n
0-m和0-n
0−m和0−n,这样的话也能表示m或者n为0的情况。
代码
// 类似0,1背包问题
class Solution {
public:
vector<vector<int>>dp;
vector<int>a0,a1;
int cal(string&str,int num){
int n=str.size();
int num_1=0;
for(char &c:str){
if(c=='1'){
num_1++;
}
}
if(num==0){
return str.size()-num_1;
}
return num_1;
}
int findMaxForm(vector<string>& strs, int m, int n) {
dp.resize(m+1);
int i=0;
for(vector<int>&v:dp){
v.resize(n+1);
fill(dp[i].begin(),dp[i].end(),0);
i++;
}
a0.resize(strs.size());
a1.resize(strs.size());
for(i=0;i<strs.size();i++){
a0[i]=cal(strs[i],0);
a1[i]=cal(strs[i],1);
// cout<<a0[i]<<" "<<a1[i]<<endl;
}
for(int k=0;k<strs.size();k++){
for(int i=m;i>=0;i--){
for(int j=n;j>=0;j--){
if(i>=a0[k]&&j>=a1[k]){
dp[i][j]=max(dp[i][j],dp[i-a0[k]][j-a1[k]]+1);
}
}
}
}
printf("%d\n",dp[m][n]);
return dp[m][n];
}
};