LeetCode638-大礼包 记忆化搜索
1.题目
在 LeetCode 商店中, 有 n 件在售的物品。每件物品都有对应的价格。然而,也有一些大礼包,每个大礼包以优惠的价格捆绑销售一组物品。
给你一个整数数组 price 表示物品价格,其中 price[i] 是第 i 件物品的价格。另有一个整数数组 needs 表示购物清单,其中 needs[i] 是需要购买第 i 件物品的数量。
还有一个数组 special 表示大礼包,special[i] 的长度为 n + 1 ,其中 special[i][j] 表示第 i 个大礼包中内含第 j 件物品的数量,且 special[i][n] (也就是数组中的最后一个整数)为第 i 个大礼包的价格。
返回 确切 满足购物清单所需花费的最低价格,你可以充分利用大礼包的优惠活动。你不能购买超出购物清单指定数量的物品,即使那样会降低整体价格。任意大礼包可无限次购买。
price = [2,5], special = [[3,0,5],[1,2,10]], needs = [3,2]
输出:14
//购买speical[1] 两个,和两个b,共14
2.思路
-
因为不能购买物品超过needs,所以先过滤掉special数组中大于needs中的元素
-
假设不购买大礼包,直接购买物品的价格为p,使用dp[needs]存储满足needs所需要的最低价格,此时
\(dp[needs]=min\{p,min\{price_i+dp[needs-needs_i]\}\}\)
i表示当前礼包中的物品个数,\(needs-needs_i\)为剩余需要购买的物品
-
采用递归解决问题,因为需要计算很多次相同的输入,我们将needs所需要的最小价格记录下来,下次遇到可直接返回
3.Code
class Solution {
public:
//存储已经计算过的needs
map<vector<int>,int> cache;
int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {
//不能使用的大礼包(不能超出购物清单指定的数量)
vector<vector<int>> cur;
int len=price.size();
for(int i=0;i<special.size();i++){
bool ok=true;
for(int j=0;j<len;j++){
if(special[i][j]>needs[j]){
ok=false;
break;
}
}
if(ok){
cur.push_back(special[i]);
}
}
return dfs(price,cur,needs);
}
int dfs(vector<int>& price,vector<vector<int>>& special,vector<int>& needs){
//遇到已经计算过的结果直接返回
if(cache[needs]){
return cache[needs];
}
//计算直接购买的结果
int total=0;
for(int i=0;i<needs.size();i++){
total+=needs[i]*price[i];
}
//遍历每个礼包
for(int i=0;i<special.size();i++){
//存储下一次需要购买的物品
vector<int> next=needs;
//判断能不能买该礼包
bool valid=true;
for(int j=0;j<price.size();j++){
if(special[i][j]>needs[j]){
valid=false;
break;
}
}
if(valid){
//next存储下次要购买的礼包
for(int j=0;j<price.size();j++){
next[j]-=special[i][j];
}
//dfs(price,special,next)表示当前购买情况下,继续购买价格最小的方式,special[i].back()表示这次购买礼包的价格
total=min(total,dfs(price,special,next)+special[i].back());
}
}
cache[needs]=total;
return total;
}
};