问题来源:leetcode 474。
问题描述
给你一个二进制字符串数组 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 。
解题思路
本题与 0-1 背包问题很相似,只不过:
- 将一个背包容量变成了两个容量,对应的动态规划使用的 d p dp dp 数组也要相应地增加一维;
- 每个字符串的价值都是 1.
优化子结构:也就是要证明原问题的最优解包含其子问题的最优解,或者说原问题的最优解可以由子问题的最优解构造得到(当然最优解可能不问唯一,这里指的是其中一个最优解)。
假设
<
X
1
,
.
.
.
,
X
n
−
1
,
X
n
>
<X_1,...,X_{n-1},X_n>
<X1,...,Xn−1,Xn> 是原问题(考虑全部字符串,子集剩余容量为
m
m
m 和
n
n
n)的一个最优解,其中
X
i
X_i
Xi 表示字符串
i
i
i 被放入了子集中,该最优解可以使取得的字符串子集中的字符串个数最大,那么
<
X
1
,
.
.
.
,
X
n
−
1
>
<X_1,...,X_{n-1}>
<X1,...,Xn−1> 是子问题(仅不考虑最后一个字符串,子集剩余容量为
m
−
X
n
⋅
W
n
,
0
m-X_n \cdot W_{n,0}
m−Xn⋅Wn,0 和
n
−
X
n
⋅
W
n
,
1
n - X_n \cdot W_{n,1}
n−Xn⋅Wn,1)的一个最优解,也就是说需要处理这两个子问题,可以使用反证法来证明:假设
<
X
1
,
.
.
.
,
X
n
−
1
>
<X_1,...,X_{n-1}>
<X1,...,Xn−1> 不是子问题的最优解,那么存在一个使得子集中单词个数更多的解
<
X
1
′
,
.
.
.
,
X
n
−
1
′
>
<X^{'}_1,...,X^{'}_{n-1}>
<X1′,...,Xn−1′>,使得子集中的字符串个数更多,那么在此基础上再考虑最后一个字符串可以使得原问题的结果中也包含更多的字符串,与
<
X
1
,
.
.
.
,
X
n
−
1
,
X
n
>
<X_1,...,X_{n-1},X_n>
<X1,...,Xn−1,Xn> 是最优解矛盾。
重叠子问题:简单地使用递归算法来计算,很容易证明是存在重叠子问题的。
递归地定义最优解的值:也就是考虑如何用子问题的最优解来构造原问题的最优解,结合优化子结构,设 d p ( i , j , k ) dp(i, j, k) dp(i,j,k) 表示子集总容量为 j j j 和 k k k 时考虑前 i i i 个字符串的字符串个数,面对当前字符串 i i i 有两种可能:
-
第一,子集的容量比该字符串所需的容量要小( j < W i , 0 j<W_{i,0} j<Wi,0 或 k < W i , 1 k < W_{i,1} k<Wi,1),那么肯定放不下,此时的 d p ( i , j , k ) dp(i,j,k) dp(i,j,k) 其实就是 d p ( i − 1 , j , k ) dp(i-1,j,k) dp(i−1,j,k)。
-
第二,子集的容量大于等于字符串的重量,那么可以放也可以不放,如何决定?看放或者不放哪个的代价更高,所以:
d p ( i , j , k ) = m a x { d p ( i − 1 , j , k ) , d p ( i − 1 , j − W i , 0 , k − W i , 1 ) + 1 } dp(i,j,k)=max\{\ dp(i-1,j,k), \ dp(i-1,j-W_{i,0},k-W_{i,1})+1\ \} dp(i,j,k)=max{ dp(i−1,j,k), dp(i−1,j−Wi,0,k−Wi,1)+1 }
不做空间优化的版本
自底向上地计算最优解的值。
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
int str_num = strs.size();
// 不做空间优化
vector<vector<vector<int>>> dp(str_num + 1, vector<vector<int>>(m + 1, vector<int>(n + 1, 0)));
for(int i=1; i<=str_num; i++) {
string& str = strs[i - 1];
int zero_num = count(str.begin(), str.end(), '0');
int one_num = count(str.begin(), str.end(), '1');
// 这里每个“物品”的代价可能为 0
for(int j=0; j<=m; j++) {
for(int k=0; k<=n; k++) {
if(j < zero_num || k < one_num) {
dp[i][j][k] = dp[i-1][j][k];
} else {
dp[i][j][k] = max(dp[i-1][j][k], dp[i-1][j-zero_num][k-one_num] + 1);
}
}
}
}
return dp[str_num][m][n];
}
};
空间优化版本
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
int str_num = strs.size();
// 做空间优化
vector<vector<int>> dp(m+1, vector<int>(n+1));
for(string& str : strs) {
int zero_num = count(str.begin(), str.end(), '0');
int one_num = count(str.begin(), str.end(), '1');
for(int j = m; j >=0; j--) {
for(int k = n; k >= 0; k--) {
if(j < zero_num || k < one_num) {
dp[j][k] = dp[j][k];
} else {
dp[j][k] = max(dp[j][k], dp[j-zero_num][k-one_num] + 1);
}
}
}
}
return dp[m][n];
}
};