139. 单词拆分
心得
- 字典是物品,目标的结果是背包,考虑装满背包时想的是字符串拼接或者个数等不等,没有好好利用string 查找函数和用set保存的快速检索
题解
- 排列问题,背包在外循环,物品在内循环,初始值取有迭代意义的值,不一定需要实际意义
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
vector<bool> dp(s.size() + 1, false); // dp[i] 表示字符串长度为i时,true表示能被字典拆分
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
dp[0] = true;
for (int i = 1; i <= s.size(); i++) { // 先背包,后物品
for (int j = 0; j < i; j++) { // 一般完全背包循环条件能写到=,但是考虑到substr的函数,没有写到
string word = s.substr(j, i - j); // substr(开始位置,长度)
if (wordSet.find(word) != wordSet.end() && dp[j] == true) {
dp[i] = true;
}
}
}
return dp[s.size()];
}
};
多重背包
- 在01背包的基础上,每个物品的个数有nums组成,这也暗示其求解办法可以将01背包的value和weight扩容
题解
void test_multi_pack() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
vector<int> nums = {2, 3, 2};
int bagWeight = 10;
for (int i = 0; i < nums.size(); i++) {
while (nums[i] > 1) { // nums[i]保留到1,把其他物品都展开
weight.push_back(weight[i]);
value.push_back(value[i]);
nums[i]--;
}
}
vector<int> dp(bagWeight + 1, 0);
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
for (int j = 0; j <= bagWeight; j++) {
cout << dp[j] << " ";
}
cout << endl;
}
cout << dp[bagWeight] << endl;
}
int main() {
test_multi_pack();
}
背包问题总结
按照递归五部曲来选择(每个都很重要,递推公式和遍历顺序一般有模板)
- 确定dp数组含义,下标含义和dp[i]数值含义
- 递推公式
- 初始化
- 遍历顺序
- 打印,确认dp数组
01背包
- 容量V的背包,物品N种,每个仅一个,提供weight和value,每次选或者不选01问题
- 2维dp数组时,遍历顺序先物品还是背包都可以,但是第二层需要从小到大(后一个由上和左上角推出)
- 一维dp数组只能先物品后背包容量,且第二层从大到小(保证不被覆盖)
完全背包
- 容量V的背包,物品N种,每种无限个,提供weight和value,每次不选或者选多个
- 纯完全背包问题,遍历顺序先物品还是背包都可以的且第二层循环小到大,但是题目稍微变化即对遍历有要求
- 组合数:外层物品,内层背包容量
- 排列数:外层背包容量,内层物品
递推公式
求最值一般max或min,恰好的所需办法一般求和
-
能否装满(最多装多少):dp[j] = max(dp[j], dp[j - num[i]] + num[i])
-
装满有几种方法::dp[j] += dp[j - nums[i]]
-
背包装满的最大值:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
-
装满背包所有物品最小个数(此时遍历顺序均可):dp[j] = min(dp[j - coins[i]] + 1, dp[j]);