139.单词拆分 ★
文档讲解 : 代码随想录 - 139.单词拆分
状态:再次回顾。(★:需要多次回顾并重点回顾)
本题其实不套完全背包思路来理解反而更简单易懂一点。
动态规划五部曲:
-
确定dp数组(dp table)以及下标的含义
dp[i]的定义为:字符串长度为i
的话,dp[i]
为true
,表示可以拆分为一个或多个在字典中出现的单词。 -
确定递推公式
if (wordSet.find(word) != wordSet.end() && dp[j]) dp[i] = true;
如果确定
dp[j]
是true
,且[j, i]
这个区间的子串出现在字典里,那么dp[i]一定是true
。( j < i )
。
所以递推公式是if ([j, i] 这个区间的子串出现在字典里 && dp[j]是true)
那么dp[i] = true
。 -
dp数组如何初始化
dp[0] = true ;
-
确定遍历顺序
完全背包思路: 求排列,先遍历背包,再遍历物品。(不可以换顺序!)
先确定想要确认的字符串的终止位置i
,再确定起始位置j
。 -
举例推导dp数组:
以输入:s = "leetcode", wordDict = ["leet", "code"]
为例,dp状态如图:
本题代码:
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 (wordSet.find(word) != wordSet.end() && dp[j]) {
dp[i] = true;
}
}
}
return dp[s.size()];
}
};
- 时间复杂度:
O
(
n
3
)
O(n^3)
O(n3),因为
substr
返回子串的副本是O(n)
的复杂度(这里的n
是substring
的长度) - 空间复杂度: O ( n ) O(n) O(n)
多重背包
文档讲解 : 代码随想录 - 多重背包
状态:再次回顾。
多重背包
有N
种物品和一个容量为V
的背包。第i种物品最多有Mi
件可用,每件耗费的空间是Ci
,价值是Wi
。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。
每件物品最多有Mi件可用,把Mi件摊开,其实就是一个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;
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
)
O(m × n × k)
O(m×n×k),
m
:物品种类个数,n
:背包容量,k
:单类物品数量
背包问题总结
文档讲解 : 代码随想录 - 背包问题总结
状态:再次回顾。