题目
//在 LeetCode 商店中, 有 n 件在售的物品。每件物品都有对应的价格。然而,也有一些大礼包,每个大礼包以优惠的价格捆绑销售一组物品。
//
// 给你一个整数数组 price 表示物品价格,其中 price[i] 是第 i 件物品的价格。另有一个整数数组 needs 表示购物清单,其中 needs[
//i] 是需要购买第 i 件物品的数量。
//
// 还有一个数组 special 表示大礼包,special[i] 的长度为 n + 1 ,其中 special[i][j] 表示第 i 个大礼包中内含第 j
// 件物品的数量,且 special[i][n] (也就是数组中的最后一个整数)为第 i 个大礼包的价格。
//
// 返回 确切 满足购物清单所需花费的最低价格,你可以充分利用大礼包的优惠活动。你不能购买超出购物清单指定数量的物品,即使那样会降低整体价格。任意大礼包可无限
//次购买。
//
//
//
// 示例 1:
//
//
//输入:price = [2,5], special = [[3,0,5],[1,2,10]], needs = [3,2]
//输出:14
//解释:有 A 和 B 两种物品,价格分别为 ¥2 和 ¥5 。
//大礼包 1 ,你可以以 ¥5 的价格购买 3A 和 0B 。
//大礼包 2 ,你可以以 ¥10 的价格购买 1A 和 2B 。
//需要购买 3 个 A 和 2 个 B , 所以付 ¥10 购买 1A 和 2B(大礼包 2),以及 ¥4 购买 2A 。
//
// 示例 2:
//
//
//输入:price = [2,3,4], special = [[1,1,0,4],[2,2,1,9]], needs = [1,2,1]
//输出:11
//解释:A ,B ,C 的价格分别为 ¥2 ,¥3 ,¥4 。
//可以用 ¥4 购买 1A 和 1B ,也可以用 ¥9 购买 2A ,2B 和 1C 。
//需要买 1A ,2B 和 1C ,所以付 ¥4 买 1A 和 1B(大礼包 1),以及 ¥3 购买 1B , ¥4 购买 1C 。
//不可以购买超出待购清单的物品,尽管购买大礼包 2 更加便宜。
//
//
//
// 提示:
//
//
// n == price.length
// n == needs.length
// 1 <= n <= 6
// 0 <= price[i] <= 10
// 0 <= needs[i] <= 10
// 1 <= special.length <= 100
// special[i].length == n + 1
// 0 <= special[i][j] <= 50
思路
其实本题思路很简单。就是暴力搜索。选或者不选大礼包,最终取一个最小值。
代码
jint res = 0;
public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
//price = [2,5], special = [[3,0,5],[1,2,10]], needs = [3,2]
// 不选任何大礼包,做为初始值。
res = noSpecial(price, needs);
backTrace(price, special, needs, 0, 0);
return res;
}
private int noSpecial(List<Integer> price, List<Integer> needs) {
int sum = 0;
for (int i = 0; i < needs.size(); i++) {
sum += price.get(i) * needs.get(i);
}
return sum;
}
// 枚举所有可能的大礼包
private void backTrace(List<Integer> price, List<List<Integer>> special, List<Integer> needs, int index,
int cost) {
if (res < cost) {
return;
}
// special 使用完了,如果 needs还没有满足,则需要直接使用。
if (index == special.size()) {
cost += noSpecial(price, needs);
res = Math.min(cost, res);
return;
}
List<Integer> tmpSpecial = special.get(index);
//检查此大礼包中的每一个礼物都符合要求,才能选择该大礼包
if (checkValidate(tmpSpecial, needs)) {
// 此大礼包可以选择,需要更新needs以及总共的花费。
List<Integer> newNeed = new ArrayList<>();
for (int j = 0; j < needs.size(); j++) {
newNeed.add(needs.get(j) - tmpSpecial.get(j));
}
backTrace(price, special, newNeed, index, cost + tmpSpecial.get(tmpSpecial.size() - 1));
}
// 注意,这个地方与前面的if并不是if-else关系。无论前面能不能使用大礼包,这里都必须有。并不是前面能用大礼包了,这里就不需要了。
// 这个是枚举所有可能,并不是贪心。
backTrace(price, special, needs, index + 1, cost);
}
private boolean checkValidate(List<Integer> tmpSpecial, List<Integer> needs) {
for (int j = 0; j < needs.size(); j++) {
if (tmpSpecial.get(j) > needs.get(j)) {
return false;
}
}
return true;
}
代码2:回溯。这种方法与常规的套路一样。很容易想出来。
public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
//price = [2,5], special = [[3,0,5],[1,2,10]], needs = [3,2]
// 不选任何大礼包,做为初始值。
return backTrace(price, special, needs);
}
private int noSpecial(List<Integer> price, List<Integer> needs) {
int sum = 0;
for (int i = 0; i < needs.size(); i++) {
sum += price.get(i) * needs.get(i);
}
return sum;
}
// 枚举所有可能的大礼包
private int backTrace(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
int noSpecial = noSpecial(price, needs);
int n = special.size();
for (int i = 0; i < n; i++) {
List<Integer> tmpSpecial = special.get(i);
if (checkValidate(tmpSpecial, needs)) {
//设置值
for (int j = 0; j < needs.size(); j++) {
needs.set(j, needs.get(j) - tmpSpecial.get(j));
}
noSpecial = Math.min(backTrace(price, special, needs) + tmpSpecial.get(tmpSpecial.size() - 1), noSpecial);
//恢复值
for (int j = 0; j <needs.size(); j++) {
needs.set(j, needs.get(j) + tmpSpecial.get(j));
}
}
}
return noSpecial;
}
private boolean checkValidate(List<Integer> tmpSpecial, List<Integer> needs) {
for (int j = 0; j < needs.size(); j++) {
if (tmpSpecial.get(j) > needs.get(j)) {
return false;
}
}
return true;
}
}
总结
从这个题可以看出递归与回溯的区别
递归: 一条路走到底
回溯: 一条路走到底,不对时可以重新选择。因此多了一个撤销的过程。
backTrace(){
//1 . 终止条
//3. for (选择){
// 处理某种变量
backTrace()
// 复原某种变量
}
}
递归其实只是回溯其中的一步。
其实本题本质就是回溯。上面虽然看着是dfs,其实最后一步就类似于一个for循环,不过只有2个元素。
if (checkValidate(tmpSpecial, needs)) //类似于设置值
backTrace(price, special, needs, index + 1, cost); //类似于撤销之前的处理。
第一种方法
private void backTrace(List price, List<List> special, List needs, int index,
int cost) {
第二种方法
backTrace(List price, List<List> special, List needs)