文章目录
- Leetcode 139.单词拆分
- 卡码网 56. 携带矿石资源
- 方法一: 分组转化成01背包
- 方法二: 转化成01背包+完全背包(基于方法一的小优化)
- 方法三: 二进制优化(优化了方法一的分组方式)
Leetcode 139.单词拆分
题目链接:Leetcode 139.单词拆分
题目描述: 给你一个字符串 s
和一个字符串列表 wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s
则返回 true
。注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
思路: 由于单词可以重复使用,并且要求正确顺序,因此可以联想到完全背包中的求排列问题。
dp[i]
: 字符串长度为i
的话,dp[i]
为true
,表示可以拆分为一个或多个在字典中出现的单词。- 递推公式:如果截取的单词
[i - sz, sz]
等于word
并且dp[i - sz] == true
,则dp[i] = true
- 初始化:
dp[0] = ture
- 遍历顺序:先遍历背包,后遍历物品。
代码如下:
class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {vector<bool> dp(s.size() + 5);dp[0] = true;// 完全背包求排列问题for (int i = 1; i <= s.size(); i++) // 先遍历背包for (auto& word : wordDict) { // 后遍历物品int sz = word.size();// 首先背包容量要大于单词大小// 其次,如果截取的单词[i-sz,sz]等于word并且dp[i-sz]==true,则dp[i]=trueif (i >= sz && s.substr(i - sz, sz) == word) {dp[i] = dp[i] || dp[i - sz];}}return dp[s.size()];}
};
- 时间复杂度 O ( n 2 ) O(n^2) O(n2)
- 空间复杂度 O ( n ) O(n) O(n)
卡码网 56. 携带矿石资源
题目链接:卡码网 56. 携带矿石资源
思路: 这道题是多重背包的模板题。首先观察多重背包的特点是每件物品的数量不相同,其余的条件和01背包和完全背包很像,那能不能将其转化成它们呢?答案是可以。
方法一: 分组转化成01背包
由于多重背包每件物品数量不相同,不知道一种占用空间 w
,价值为 v
,数量为 s
的物品该拿多少件,因此我们就把该拿多少件枚举一下,假设为 k
件1 <= k <= s
,然后我们就把问题看成仅有一件的占用空间k * w
,价值为k * v
的物品该不该拿。
代码如下:
#include <iostream>
#include <vector>using namespace std;const int N = 1e4 + 5;int w[N],v[N],k[N];
int dp[N];
int n,m;int main()
{cin >> n >> m;//背包大小和物品数量for(int i = 1; i <= m; i ++ ) cin >> w[i];for(int i = 1; i <= m; i ++ ) cin >> v[i];for(int i = 1; i <= m; i ++ ) cin >> k[i];//转化成01背包for(int i = 1; i <= m; i ++ )//遍历物品for(int j = n; j >= w[i]; j -- )//遍历背包//遍历个数for(int num = 1; num <= k[i] && j - num * w[i] >= 0; num ++ )dp[j] = max(dp[j], dp[j - num * w[i]] + num * v[i]);cout << dp[n];return 0;
}
- 时间复杂度 O ( n 3 ) O(n^3) O(n3)
- 空间复杂度 O ( n ) O(n) O(n)
方法二: 转化成01背包+完全背包(基于方法一的小优化)
我们发现,如果k
个物品的总重量大于等于背包的最大容量,说明在当前大小的背包容量下,该物品有无限个和只有k
个的效果是相同的,这部分物品就转化成了完全背包问题。
为什么要转换成完全背包?我们看方法一转化成01背包是需要枚举一下拿多少件的,而转化为完全背包是不需要枚举多少件的,可以拿我们就拿,所以在时间上会有一些优化。(尽管时间复杂度不变)
代码如下:
#include <iostream>
#include <vector>using namespace std;const int N = 1e4 + 5;int w[N],v[N],k[N];
int dp[N];
int n,m;int main()
{ cin >> n >> m;//背包大小和物品数量for(int i = 1; i <= m; i ++ ) cin >> w[i];for(int i = 1; i <= m; i ++ ) cin >> v[i];for(int i = 1; i <= m; i ++ ) cin >> k[i];//转化成01背包+完全背包for(int i = 1; i <= m; i ++ )//遍历物品{if(k[i] * w[i] >= n)//如果不能把某个物品全部一次性装下,则可转化为完全背包{for(int j = w[i]; j <= n; j ++ )dp[j] = max(dp[j], dp[j - w[i]] + v[i]);}else{for(int j = n; j >= w[i]; j -- )//遍历背包//遍历个数for(int num = 1; num <= k[i] && j - num * w[i] >= 0; num ++ )dp[j] = max(dp[j], dp[j - num * w[i]] + num * v[i]);}}cout << dp[n];return 0;
}
- 时间复杂度 O ( n 3 ) O(n^3) O(n3)
- 空间复杂度 O ( n ) O(n) O(n)
方法三: 二进制优化(优化了方法一的分组方式)
我们发现利用从 1 1 1 、 2 2 2、 4 4 4…… 2 n 2^n 2n可以枚举出 [ 0 , 2 n − 1 ] [0,2^n-1] [0,2n−1]的所有数字(利用等比数列求和可以证明),因此可以不用像方法一那样依次枚举每个数字,本题数据范围是 1 0 4 10^4 104,而 2 13 = 8192 2^{13}=8192 213=8192,也就是每个数字最多分为 14 14 14组即可,不必依次枚举k
,大大降低了时间复杂度。
代码如下:
#include <iostream>
#include <vector>using namespace std;const int N = 1e4 + 5;
const int M = 13 * N + 5;//根据数据范围,一个数最多拆13次,因此要比原来多开一些空间
int w[N],v[N],k[N];
int W[M],V[M];//用来记录分组之后的物品重量和价值
int dp[N];
int n,m;int main()
{ cin >> n >> m;//背包大小和物品数量for(int i = 1; i <= m; i ++ ) cin >> w[i];for(int i = 1; i <= m; i ++ ) cin >> v[i];for(int i = 1; i <= m; i ++ ) cin >> k[i];//将物品数量按照(1,2,4...2^n)分组,然后转化为01背包(优化了遍历次数)int cnt = 0;//记录物品数量for(int i = 1; i <= m; i ++ )//分组过程{int num = 1;while(num <= k[i]){cnt ++ ;W[cnt] = num * w[i];V[cnt] = num * v[i];k[i] -= num;num *= 2;}if(k[i])//剩下的直接放入{cnt ++ ;W[cnt] = k[i] * w[i];V[cnt] = k[i] * v[i];}}//01背包过程for(int i = 1;i <= cnt; i ++ )for(int j = n; j >= W[i]; j -- ){dp[j] = max(dp[j], dp[j - W[i]] + V[i]);}//输出cout << dp[n];return 0;
}
- 时间复杂度 O ( n 2 l o g n ) O(n^2logn) O(n2logn)
- 空间复杂度 O ( n ) O(n) O(n)
总结: 遇到一个新问题首先我们可以思考是否可以利用已有知识来解决,就像多重背包的解法就是基于01背包和完全背包。
最后,如果文章有错误,请在评论区或私信指出,让我们共同进步!