这题就离谱,剪枝反而更慢了。
这里我没有使用官方的记忆化搜索,而是通过控制选取礼包的起点,也就是函数中的那个参数st,来保证不重复选取礼包的组合,事实证明我的做法更优。
这里回溯的时候,记得要传一个new ArrayList<>(needs),不然传过去的其实是指针,会修改原来的needs,跌坑好几次了orz
思路的话没啥好说的,就是硬搜,毫无技巧可言,有点像那个完全背包。
import java.util.ArrayList;
import java.util.List;
class Solution {
List<Integer> price;
int len;
List<List<Integer>> special;
int ans = Integer.MAX_VALUE;
public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
this.price = price;
len = price.size();
this.special = special;
dfs(needs, 0, 0);
return ans;
}
// now 为已经花费的金额
// st 为选取st以及之后的礼包,是为了不重复选取顺序不同但礼包相同的组合
void dfs(List<Integer> needs, int now, int st) {
// sum为直接单买的价格
int sum = now;
for (int i = 0; i < len; i++) {
sum += price.get(i) * needs.get(i);
}
ans = Math.min(sum, ans);
// 各种礼包选购一遍,状态转移
for (int i = st; i < special.size(); i++) {
List<Integer> temp = new ArrayList<>(needs);
List<Integer> list = special.get(i);
boolean flag = true;
for (int j = 0; j < len; j++) {
int after = needs.get(j) - list.get(j);
if (after < 0) {
flag = false;
break;
}
temp.set(j, after);
}
if (flag) {
dfs(temp, now + list.get(len), i);
}
}
}
}
减速的剪枝
// 剪枝 无用的礼包以及某个商品过多的礼包删掉
for (List<Integer> ASpecial : special) {
int sum = 0;
boolean flag = true;
for (int j = 0; j < len; j++) {
if (ASpecial.get(j) > needs.get(j)) {
flag = false;
break;
}
sum += price.get(j) * ASpecial.get(j);
}
if (sum >= ASpecial.get(len) && flag) {
trueSpecial.add(ASpecial);
}
}
this.special = trueSpecial;