在 LeetCode 商店中, 有 n
件在售的物品。每件物品都有对应的价格。然而,也有一些大礼包,每个大礼包以优惠的价格捆绑销售一组物品。
给你一个整数数组 price
表示物品价格,其中 price[i]
是第 i
件物品的价格。另有一个整数数组 needs
表示购物清单,其中 needs[i]
是需要购买第 i
件物品的数量。
还有一个数组 special
表示大礼包,special[i]
的长度为 n + 1
,其中 special[i][j]
表示第 i
个大礼包中内含第 j
件物品的数量,且 special[i][n]
(也就是数组中的最后一个整数)为第 i
个大礼包的价格。
返回确切满足足购物清单所需花费的最低价格,你可以充分利用大礼包的优惠活动。你不能购买超出购物清单指定数量的物品,即使那样会降低整体价格。任意大礼包可无限次购买。
解题思路:
由于购买礼包一定比单个购买要划算,因此要想所花金额最少,一定要尽量购买礼包。同时题目还要求不能让购买的商品总数超过需求量,这减小了题目的难度。本题可以使用回溯思想,将所有尽可能买礼包的情况都计算一遍,比较得所花的最少金额。
假设一共有n个礼包,编号为:0,1,2……n-1,则思路为:
1.先根据商品集合needs,购买尽可能多的0号礼包(假设购买量为quantity[0]),并更新needs;之后根据更新后的needs购买尽可能多的1号礼包(假设购买量为quantity[1]),并更新needs……一直到最后尽可能多的把n-1号礼包都购买好后,再把needs剩余的部分(这些部分无法再用礼包购买)单独购买,算出一次的金额。
2.之后回溯到购买n-2号商品的情况,将n-2后商品的购买量减去1(此时购买数量为quantity[n-2]=quantity[n-2]-1),之后再尽可能多的购买n-1号商品又可以算出一次金额。之后再让n-2号商品的购买量再次减1(quantity[n-2]=quantity[n-2]-1),就这样直到n-2号商品的购买量到0位置。
3.接下来回溯到n-3号商品的,与2类似,每次令quantity[n-3]=quantity[n-3]-1,然后尽可能多的购买n-2号和n-1号商品(n-3号商品购买量每减少1,就要重复一次第2步考察所有尽可能多的购买n-2号商品和n-1号商品的情况)。之后再不断回溯到低n-4,n-5直到0号商品,这样就可以比较得出所有金额中最小的数值。
代码如下:
int res = Integer.MAX_VALUE;
public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
//优先使用大礼包
getRes(price, special, needs, 0, 0);//从第0号礼包开始使用
return res;
}
/**
* @param price
* @param special
* @param needs
* @param specialNo 代表现在需要使用的礼包的下标
* @param total 至今为止总共花费的钱
*/
private void getRes(List<Integer> price, List<List<Integer>> special, List<Integer> needs, int specialNo, int total) {
List<Integer> specialPack = special.get(specialNo);
int specialQuantity = getSpecialQuantity(special, needs, specialNo);//根据needs获取specialNo编号的大礼包最多使用几个
for (int i = specialQuantity; i >= 0; i--) {
int newTotal = total;
ArrayList<Integer> newNeeds = new ArrayList<>(needs);//复制一份needs
updataNewNeeds(newNeeds, i, specialPack);//使用specialNo号的礼包后,更新newNeeds
newTotal += i * specialPack.get(specialPack.size() - 1);//更新total的金额数
if (specialNo + 1 <= special.size() - 1) getRes(price, special, newNeeds, specialNo + 1, newTotal);
if (specialNo == special.size() - 1) {//此时已经将最后一个礼包使用结束了,剩余部分就应该单个购买
int sum = newTotal + getSumFromPrice(price, newNeeds);
res = Math.min(res, sum);
}
}
}
private void updataNewNeeds(ArrayList<Integer> newNeeds, int speciaQuantity, List<Integer> specialPack) {
for (int i = 0; i < newNeeds.size(); i++) {
newNeeds.set(i, newNeeds.get(i) - speciaQuantity * specialPack.get(i));
}
}
//获取specialNo编号的大礼包最多可以使用几个
private int getSpecialQuantity(List<List<Integer>> special, List<Integer> newNeeds, int specialNo) {
int quantity = Integer.MAX_VALUE;
List<Integer> specialPack = special.get(specialNo);//通过礼包编号确定礼包
for (int i = 0; i < newNeeds.size(); i++) {
if (specialPack.get(i) != 0) {
int temp = newNeeds.get(i) / specialPack.get(i);
if (temp == 0) return 0;//代表礼包中的商品大于所需商品,不能买这个礼包
quantity = Math.min(temp, quantity);
}
}
if (quantity == Integer.MAX_VALUE) quantity = 0;
return quantity;
}
//不能使用大礼包后,只能从price中计算金额
private int getSumFromPrice(List<Integer> price, List<Integer> needs) {
int sum = 0;
for (int i = 0; i < price.size(); i++) {
sum += needs.get(i) * price.get(i);
}
return sum;
}