2021年10月24日每日一题:
:大礼包问题
难度:中等
感悟:一顿操作猛如虎,bug查得真辛苦。思路好像都一样,一看细节多到吐。
官方题解:记忆搜索
class Solution {
public:
map<vector<int>, int> memo; //记录needs的状态和此状态下的最优解
int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {
int n = price.size(); //商品数量
// 过滤不需要计算的大礼包,只保留需要计算的大礼包
vector<vector<int>> filterSpecial;
for (auto & sp : special) {
int totalCount = 0, totalPrice = 0;
for (int i = 0; i < n; ++i) {
totalCount += sp[i]; //大礼包里总的商品数量
totalPrice += sp[i] * price[i]; //大礼包里的商品如果原价买所需钱
}
if (totalCount > 0 && totalPrice > sp[n]) { //大礼包里确定有商品and价格比原价购买低
filterSpecial.emplace_back(sp); //这个大礼包可以考虑,加入候选队伍
}
}
return dfs(price, special, needs, filterSpecial, n); //用dfs计算最优解
}
// 记忆化搜索计算满足购物清单所需花费的最低价格
int dfs(vector<int> price,const vector<vector<int>> & special, vector<int> curNeeds, vector<vector<int>> & filterSpecial, int n) {
if (!memo.count(curNeeds)) { //确定此need状态没有遇见过(记忆)
int minPrice = 0; //最优解
for (int i = 0; i < n; ++i) {
minPrice += curNeeds[i] * price[i]; // 不购买任何大礼包,原价购买购物清单中的所有物品
}
for (auto & curSpecial : filterSpecial) { //
int specialPrice = curSpecial[n]; //大礼包价格
vector<int> nxtNeeds; //记录need状态
for (int i = 0; i < n; ++i) {
if (curSpecial[i] > curNeeds[i]) { // 不能购买超出购物清单指定数量的物品
break;
}
nxtNeeds.emplace_back(curNeeds[i] - curSpecial[i]); //改变need状态
}
if (nxtNeeds.size() == n) { // 大礼包可以购买
minPrice = min(minPrice, dfs(price, special, nxtNeeds, filterSpecial, n) + specialPrice); //改变need状态后再次进入dfs进行计算
}
}
memo[curNeeds] = minPrice; //记录此need状态的最优解
}
return memo[curNeeds];
}
};
复盘:算法不会,思想有的,看到题,哐哐哐,很快就写出来代码,想法是
- 判断出符合的大礼包
- 把大礼包的数量扣了,剩下的原价购买,再加上大礼包的钱,就是答案
- 错误点:可以买多个一样大礼包,我默认只能买一个礼包
- 修改:求needs/大礼包的商品的最小公约数,用最小公约数来求大礼包数量
- 错误点:可以不同的大礼包结合,我默认只能买一种礼包
- 修改:不会了
官方题解思路:
1.找到符合条件的大礼包–有商品and大礼包的商品原价购买比大礼包贵
2.使用记忆搜索,找到最优解
记忆搜索:
1.记忆下need扣除大礼包后的状态。
2.回溯来穷举所有的大礼包和原价的组合情况。
思路差距:没有想到记忆搜索和回溯,看到题第一感觉就是遍历来不断判断情况,遇到最后一个问题,即可以不同的礼包相结合时,没有想到递归回溯的办法,依然想着遍历安判断。
学习了:当有不同的组合时,记住可以使用递归回溯来穷举出所有组合,记忆化搜索可以省去很多时间。遇见题不要老想着遍历,想好了题目里的所有情况再动手,不然就会不断出现bug。