LeetCode 638大礼包

题目

//在 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)

第一种方法的签名需要当前行的状态。第二种是遍历全部的needs,因此第2种不用再区分当前选与不选的状态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值