给你一个非空字符串 s 和一个包含非空单词的列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。
注意:
不要求字典中出现的单词全部都使用,并且字典中的单词可以 重复使用;
字典中没有重复的单词。
视频讲解https://www.bilibili.com/video/BV1pd4y147Rh文章讲解https://programmercarl.com/0139.%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86.html
- 思路:
- 回溯:分割字符串s,每个子串都能在字典中找到👉true
- 动态规划:字典中的单词(物品)可以组成字符串s(装满背包)👉true
- 物品可重复使用👉完全背包
- dp[i]:长度为 i 的字符串(s 的前 i 个)能否被字典中的单词组成👉true / false
- 递推公式:dp[j] == true 且 子串[j, i) 出现在字典中👉dp[i] = true
- 初始化:dp[0] = true;非零下标都为false
- 遍历顺序:先背包后物品(排列数)
- 代码:
// 代码随想录
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
vector<bool> dp(s.size() + 1, false);
dp[0] = true;
for (int i = 1; i <= s.size(); i++) { // 遍历背包
for (int j = 0; j < i; j++) { // 遍历物品
string word = s.substr(j, i - j); //substr(起始位置,截取的个数)
if (dp[j] && wordSet.find(word) != wordSet.end()) {
dp[i] = true;
}
}
}
return dp[s.size()];
}
};
// 时间复杂度:O(n^3),因为substr返回子串的副本是O(n)的复杂度(这里的n是substring的长度)
// 空间复杂度:O(n)
// 自己的
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
// dp[i]:拼成的长度为i的字符串
vector<string> dp(s.size() + 1, "");
// 有顺序:先背包,后物品
for (int i = 0; i <= s.size(); ++i) {
for (int j = 0; j < wordDict.size(); ++j) {
if (i >= wordDict[j].size()) {
string str = dp[i - wordDict[j].size()] + wordDict[j];
if (str > s) continue;
dp[i] = max(str, dp[i]);
}
}
}
if (dp[s.size()] == s) return true;
return false;
}
};
有 N 种物品和一个容量为 V 的背包。第 i 种物品最多有 Mi 件可用,每件占据空间 Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。
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();
}
// 时间复杂度:O(m × n × k),m:物品种类个数,n背包容量,k单类物品数量
把每种商品个数的遍历放在01背包里面👇
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;
vector<int> dp(bagWeight + 1, 0);
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
// 以上为01背包,然后加一个遍历个数
for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++) { // 遍历个数
dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]);
}
}
// 打印一下dp数组
for (int j = 0; j <= bagWeight; j++) {
cout << dp[j] << " ";
}
cout << endl;
}
cout << dp[bagWeight] << endl;
}
int main() {
test_multi_pack();
}
// 时间复杂度:O(m × n × k),m:物品种类个数,n背包容量,k单类物品数量
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
⭐递推公式:
- 能否装满背包 / 最多装多少:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
物品 i 重量、价值都是 nums[i]
LeetCdoe 416.分割等和子集 dp[j] == j👉能装满容量为 j 的背包
- 装满背包有几种方法:dp[j] += dp[j - nums[i]]
①不装物品 i:全部空间 j 分配给物品 0 ~ i - 1👉dp[j]
②装物品 i:占掉空间nums[i],剩余空间 j - nums[i] 分配给物品 0 ~ i - 1👉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])
初始化:dp[0] = 0,非零下标 = INT_MAX
避免对初始值INT_MAX直接+1导致溢出
⭐遍历顺序:
- 01背包:
- 二维dp数组:先物品、先背包都可
- 一维dp数组:只能先物品,内层for循环从后往前(保证同一个物品最多装入一次)
- 完全背包:内层for循环从前往后(保证同一个物品可装入多次)
- 组合数:只能先物品
- 排列数:只能先背包
- 不涉及具体元素组成:先物品、先背包都可
- 组合数:只能先物品