在计算机界中,我们总是追求用有限的资源获取最大的收益。
现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。
你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次。
注意:
给定 0 和 1 的数量都不会超过 100。
给定字符串数组的长度不会超过 600。
示例 1:
输入: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
输出: 4
解释: 总共 4 个字符串可以通过 5 个 0 和 3 个 1 拼出,即 "10","0001","1","0" 。
示例 2:
输入: Array = {"10", "0", "1"}, m = 1, n = 1
输出: 2
解释: 你可以拼出 "10",但之后就没有剩余数字了。更好的选择是拼出 "0" 和 "1" 。
思路分析:每一个字符串都可以放入或者不放入,放入和不放入都会对后面的选择有影响,所以这是一道典型的动态规划题。
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
int strsSize = strs.size(), maxRes = 0;
if (strsSize == 0){
return maxRes;
}
vector<int> myStrVec;//myStrVec[i]用于统计strs[i]字符串中0、1的个数,(0的个数 * 1000 + 1的个数) (因为0,1个数不会超过100
for (auto &str : strs) {
int zeroNum = 0, oneNum = 0;
//统计str串中,0,1的个数
for (auto ch : str) {
if (ch == '0') {
zeroNum += 1;
}
else {
oneNum += 1;
}
}
//组合放入myStrVec
myStrVec.push_back(zeroNum * 1000 + oneNum);
}
vector<pair<int, int> > dp(strsSize, { m * 1000 + n, 0 });//dp[i]用于记录放入第i个字符串可得到的中间最大结果
for (int i = 0; i < strsSize; ++i) {
int maxIndex = -1, tempRes = 0;//放入i时,j的最优下标
for (int j = 0; j < i; ++j) {
//如果j中剩余的个数能够放入i,并且放入之后会大于之前的结果
if (dp[j].first / 1000 >= myStrVec[i] / 1000 && dp[j].first % 1000 >= myStrVec[i] % 1000 && dp[j].second + 1 > tempRes) {
maxIndex = j;
tempRes = dp[j].second + 1;
}
}
//更新是否放入i
if (maxIndex != -1) {
//将i放入maxIndex中达到最大值
dp[i].first = (dp[maxIndex].first / 1000 - myStrVec[i] / 1000) * 1000 + (dp[maxIndex].first % 1000 - myStrVec[i] % 1000);
dp[i].second = tempRes;
}
else if (m >= myStrVec[i] / 1000 && n >= myStrVec[i] % 1000){
//否则考虑初始值放入i
dp[0].first = (m - myStrVec[i] / 1000) * 1000 + (n - myStrVec[i] % 1000);
dp[0].second = 1;
}
maxRes = max(maxRes, tempRes);
}
return maxRes;
}
};
上面的方法存在一个逻辑漏洞,因为无法保证放入i的时候让剩余的0、1的个数尽量的多,导致求出的可能是局部最优解,但是最终结果不是整体最优解。
方法:其实这是一个二维的背包问题,即将0,1的进行二维处理。
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
int strsSize = strs.size();
vector<int> myStrVec;//myStrVec[i]用于统计strs[i]字符串中0、1的个数,(0的个数 * 1000 + 1的个数) (因为0,1个数不会超过100
for (auto &str : strs) {
int zeroNum = 0, oneNum = 0;
//统计str串中,0,1的个数
for (auto ch : str) {
if (ch == '0') {
zeroNum += 1;
}
else {
oneNum += 1;
}
}
//组合放入myStrVec
myStrVec.push_back(zeroNum * 1000 + oneNum);
}
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));//dp[i][j]用于记录i个0,j个1的最优解
for (int index = 0; index < strsSize; ++index){//穷举所有字符串是否放入
for (int i = m; i >= myStrVec[index] / 1000; --i){//0的个数
for (int j = n; j >= myStrVec[index] % 1000; --j){//1的个数
dp[i][j] = max(dp[i][j], dp[i - myStrVec[index] / 1000][j - myStrVec[index] % 1000] + 1);
}
}
}
return dp[m][n];
}
};
由于上面的算法多次计算myStrVec[index] / 1000,myStrVec[index] % 1000,可以使用变量保存。
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
int strsSize = strs.size();
vector<int> myStrVec;//myStrVec[i]用于统计strs[i]字符串中0、1的个数,(0的个数 * 1000 + 1的个数) (因为0,1个数不会超过100
for (auto &str : strs) {
int zeroNum = 0, oneNum = 0;
//统计str串中,0,1的个数
for (auto ch : str) {
if (ch == '0') {
zeroNum += 1;
}
else {
oneNum += 1;
}
}
//组合放入myStrVec
myStrVec.push_back(zeroNum * 1000 + oneNum);
}
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));//dp[i][j]用于记录i个0,j个1的最优解
for (int index = 0; index < strsSize; ++index){
//str[index]需要的0的个数needM,str[index]需要的1的个数needN
int needM = myStrVec[index] / 1000, needN = myStrVec[index] % 1000;
for (int i = m; i >= needM; --i){
for (int j = n; j >= needN; --j){
dp[i][j] = max(dp[i][j], dp[i - needM][j - needN] + 1);
}
}
}
return dp[m][n];
}
};
这样更方便理解。