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;
    }
};
posted @ 2021-10-24 18:36  流光之中  阅读(34)  评论(0编辑  收藏  举报