觉得是排列问题,完全背包。原本这样写的,不熟悉字符串的接口,编译不通过。
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
map <string,int> dp;
dp[""]=1;
for(int i=1;i<s.size();i++){
for(int j=0;j<wordDict.size();j++){
if(dp[s.substr(0,i)]==0&&string(s.substr(0,i)).endswith(string(wordDict[j])))
dp[s.substr(0,i)]=dp[s.substr(0,i-wordDict[j].size())]&1
}
}
if(dp[s]==0) return false;
return true;
}
};
回溯法是暴力解法,会超时。题解:
class Solution {
private:
bool backtracking (const string& s, const unordered_set<string>& wordSet, int startIndex) {
if (startIndex >= s.size()) {
return true;
}
for (int i = startIndex; i < s.size(); i++) {
string word = s.substr(startIndex, i - startIndex + 1);
if (wordSet.find(word) != wordSet.end() && backtracking(s, wordSet, i + 1)) {
return true;
}
}
return false;
}
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
return backtracking(s, wordSet, 0);
}
};
记忆化递推(题解)
class Solution {
private:
bool backtracking (const string& s,
const unordered_set<string>& wordSet,
vector<bool>& memory,
int startIndex) {
if (startIndex >= s.size()) {
return true;
}
// 如果memory[startIndex]不是初始值了,直接使用memory[startIndex]的结果
if (!memory[startIndex]) return memory[startIndex];
for (int i = startIndex; i < s.size(); i++) {
string word = s.substr(startIndex, i - startIndex + 1);
if (wordSet.find(word) != wordSet.end() && backtracking(s, wordSet, memory, i + 1)) {
return true;
}
}
memory[startIndex] = false; // 记录以startIndex开始的子串是不可以被拆分的
return false;
}
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
vector<bool> memory(s.size(), 1); // -1 表示初始化状态
return backtracking(s, wordSet, memory, 0);
}
};
看了题解后写的代码:
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> mp(wordDict.begin(),wordDict.end());
vector<int> dp(s.size()+1,0);
dp[0]=1;
for(int i=1;i<=s.size();i++){
for(int j=0;j<i;j++){
string tmp=s.substr(j,i-j);
if(mp.find(tmp)!=mp.end()&&dp[j])
dp[i]=1;
}
}
if(dp[s.size()]==0) return false;
return true;
}
};
多重背包:
有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。可以转成01背包问题。
转成01背包的代码如下,但是超时了。主要消耗在vector的动态底层扩容上。
for (int i = 0; i < n; i++) {
while (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++) { // 遍历物品,注意此时的物品数量不是n
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
另一种方式:就是把每种商品遍历的个数放在01背包里面在遍历一遍。
for(int i = 0; i < n; 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]);
}
}
}
时间复杂度:O(m × n × k),m:物品种类个数,n背包容量,k单类物品数量。
当然还有那种二进制优化的方法,其实就是把每种物品的数量,打包成一个个独立的包。
和以上在循环遍历上有所不同,因为是分拆为各个包最后可以组成一个完整背包,具体原理我就不做过多解释了,大家了解一下就行,面试的话基本不会考完这个深度了,感兴趣可以自己深入研究一波。
总结:
多重背包在面试中基本不会出现,力扣上也没有对应的题目,大家对多重背包的掌握程度知道它是一种01背包,并能在01背包的基础上写出对应代码就可以了。
至于背包九讲里面还有混合背包,二维费用背包,分组背包等等这些,大家感兴趣可以自己去学习学习,这里也不做介绍了,面试也不会考。
总结:01背包:一维dp数组01背包只能先遍历物品再遍历背包容量,且第二层for循环是从大到小遍历。一维dp数组01背包只能先遍历物品再遍历背包容量,且第二层for循环是从大到小遍历。完全背包:如果求组合数就是外层for循环遍历物品,内层for遍历背包。如果求排列数就是外层for遍历背包,内层for循环遍历物品。对于背包问题,其实递推公式算是容易的,难是难在遍历顺序上,如果把遍历顺序搞透,才算是真正理解了。