638. Shopping Offers

60 篇文章 1 订阅
6 篇文章 0 订阅

In LeetCode Store, there are some kinds of items to sell. Each item has a price.

However, there are some special offers, and a special offer consists of one or more different kinds of items with a sale price.

You are given the each item’s price, a set of special offers, and the number we need to buy for each item. The job is to output the lowest price you have to pay for exactly certain items as given, where you could make optimal use of the special offers.

Each special offer is represented in the form of an array, the last number represents the price you need to pay for this special offer, other numbers represents how many specific items you could get if you buy this offer.

You could use any of special offers as many times as you want.

Example 1:
Input: [2,5], [[3,0,5],[1,2,10]], [3,2]
Output: 14
Explanation:
There are two kinds of items, A and B. Their prices are $2 and $5 respectively.
In special offer 1, you can pay $5 for 3A and 0B
In special offer 2, you can pay $10 for 1A and 2B.
You need to buy 3A and 2B, so you may pay $10 for 1A and 2B (special offer #2), and $4 for 2A.
Example 2:
Input: [2,3,4], [[1,1,0,4],[2,2,1,9]], [1,2,1]
Output: 11
Explanation:
The price of A is $2, and $3 for B, $4 for C.
You may pay $4 for 1A and 1B, and $9 for 2A ,2B and 1C.
You need to buy 1A ,2B and 1C, so you may pay $4 for 1A and 1B (special offer #1), and $3 for 1B, $4 for 1C.
You cannot add more items, though only $9 for 2A ,2B and 1C.
Note:
There are at most 6 kinds of items, 100 special offers.
For each item, you need to buy at most 6 of them.
You are not allowed to buy more items than you want, even if that would lower the overall price.

问题描述

购买一批东西,可以原价购买,可以打折促销购买,求满足购买数量的最小的总价钱。

代码实现

1、暴力DFS(3ms)

 public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
    	return helper(price, special, needs, 0);
    }
    
    private int helper(List<Integer> price, List<List<Integer>> special, List<Integer> needs, int pos) {
    	int local_min = directPurchase(price, needs);
    	for (int i = pos; i < special.size(); i++) {
    		List<Integer> offer = special.get(i);
    		List<Integer> temp = new ArrayList<Integer>();
        	for (int j= 0; j < needs.size(); j++) {
        	   //当前的special不满足
        		if (needs.get(j) < offer.get(j)) { // check if the current offer is valid
        			temp =  null;
        			break;
        		}
        		temp.add(needs.get(j) - offer.get(j));
        	}
        	//使用special购买+剩下的need的DFS总价钱
    		if (temp != null) { // use the current offer and try next
    			local_min = Math.min(local_min, offer.get(offer.size() - 1) + helper(price, special, temp, i)); 
    		}
    	}

    	return  local_min;
    }
    //直接购买的总价钱
    private int directPurchase(List<Integer> price, List<Integer> needs) {
    	int total = 0;
    	for (int i = 0; i < needs.size(); i++) {
    		total += price.get(i) * needs.get(i);
    	}	
    	return total;
    }

DFS的优化

2、Backtracking with two optimizations (8ms)

  • 防止backtracking中重复计算,记忆化搜索, Map<needs, minimum value for this needs>
  • 防止计算完了special offer 1, specifal offer 2,再次计算special offer 2, specifal offer 1的情况

class Solution {
    public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
        HashMap<List<Integer>, Integer> map = new HashMap<>();
        return helper(price, special, needs, 0, map);
    }

     private int helper(List<Integer> price, List<List<Integer>> special, List<Integer> needs, int pos,HashMap<List<Integer>, Integer> map) {
         
              //边界值
            if (price == null || price.size() == 0 || needs == null || needs.size() == 0) {
                return 0;
            }
            //如果这个need输入对应的总价钱已经算过直接返回
            if (map.containsKey(needs)) {
                return map.get(needs);
            }
            int local_min = directPurchase(price, needs);
            for (int i = pos; i < special.size(); i++) {
                List<Integer> offer = special.get(i);
                List<Integer> temp = new ArrayList<Integer>();
                for (int j= 0; j < needs.size(); j++) {
                   //当前的special不满足
                    if (needs.get(j) < offer.get(j)) { // check if the current offer is valid
                        temp =  null;
                        break;
                    }
                    temp.add(needs.get(j) - offer.get(j));
                }
                //使用special购买+剩下的need的DFS总价钱
                if (temp != null) { // use the current offer and try next
                    local_min = Math.min(local_min, offer.get(offer.size() - 1) + helper(price, special, temp, i,map)); 
                }
            }
            map.put(needs, local_min);
            return  local_min;
        }
        //直接购买的总价钱
        private int directPurchase(List<Integer> price, List<Integer> needs) {
            int total = 0;
            for (int i = 0; i < needs.size(); i++) {
                total += price.get(i) * needs.get(i);
            }	
            return total;
        }

}

其他思路

1、回溯法 (30ms)

The basic idea is to pick each offer, and subtract the needs. And then compute the price without the offer.
Pick whichever is minimum.

public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
    int result = Integer.MAX_VALUE;
    //apply each offer to the needs, and recurse
    for(int i = 0; i < special.size(); i++) {
        List<Integer> offer = special.get(i);
        boolean invalidOffer = false;
        for(int j = 0; j < needs.size(); j++) { // subtract offer items from needs
            int remain = needs.get(j) - offer.get(j);
            needs.set(j, remain);
            if(!invalidOffer && remain < 0) invalidOffer = true; // if offer has more items than needs
        }
        if(!invalidOffer) { //if valid offer, add offer price and recurse remaining needs
            result = Math.min(result, shoppingOffers(price, special, needs) + offer.get(needs.size()));
        }
        for(int j = 0; j < needs.size(); j++) { // reset the needs
            int remain = needs.get(j) + offer.get(j);
            needs.set(j, remain);
        }
    }
    // choose b/w offer and non offer
    int nonOfferPrice = 0;
    for(int i = 0; i < needs.size(); i++) {
        nonOfferPrice += price.get(i) * needs.get(i);
    }
    return Math.min(result, nonOfferPrice);
}

问题1

回溯太多次数,导致指数级别复杂度。

UPDATE 1 : For the below test case, we get time limit exceeded 
since it's exponential. TLE due to needs=30+.
I've requested admins to add this testcase.

[2,5]
[[1,0,5],[1,2,10]]
[39,39]

优化

提前算出每个offer可能被需要的次数,贪心的思想。 (55ms)

So I made some optimization to reduce the recursive calls, by precomputing the number of times offer can be applied. See UPDATE 3, there’s an example that breaks this greedy optimization.

public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
    int result = Integer.MAX_VALUE;
    //apply each offer to the needs, and recurse
    for(int i = 0; i < special.size(); i++) {
        List<Integer> offer = special.get(i);
        boolean invalidOffer = false;
        int offerCount = Integer.MAX_VALUE; // number of times offer can be applied
        for(int j = 0; j < needs.size(); j++) { // pre-compute number of times offer can be called
            int remain = needs.get(j) - offer.get(j);
            if(!invalidOffer && remain < 0) invalidOffer = true; // if offer has more items than needs
            if(offer.get(j) > 0)
            offerCount = Math.min(offerCount, needs.get(j)/offer.get(j));
        }
        for(int j = 0; j < needs.size(); j++) { // subtract offer items from needs
            int remain = needs.get(j) - offer.get(j) * offerCount;
            needs.set(j, remain);
        }
        if(!invalidOffer) { //if valid offer, add offer price and recurse remaining needs
            result = Math.min(result, shoppingOffers(price, special, needs) + (offerCount * offer.get(needs.size())));
        }

        for(int j = 0; j < needs.size(); j++) { // reset the needs
            int remain = needs.get(j) + offer.get(j) * offerCount;
            needs.set(j, remain);
        }
    }

    // choose b/w offer and non offer
    int nonOfferPrice = 0;
    for(int i = 0; i < needs.size(); i++) {
        nonOfferPrice += price.get(i) * needs.get(i);
    }
    return Math.min(result, nonOfferPrice);
}

问题2
UPDATE 2: I think OJ is breaking with the below test case. My code handles it though. Expected output is 8000, since it has two items of 1$ each. I’ve requested to add the test case. Also, another assumption is that result doesn’t exceed Integer.MAX_VALUE. @administrators

[1,1]
[[1,1,2],[1,1,3]]
[4000,4000]

问题3
UPDATE 3: From @Red_Eden 's thought, I found a test case that breaks my optimization. OJ is missing this test as well. My solution gives answer = 6, but actual is pick one offer just once = 4.

[500]
[[2,1],[3,2],[4,1]]
[9]

总结

题目的输入限制是数量不能买多了,并且每个数量 0 <= N <=6,这个数量上贪心好像也是对的,没证明,带入了几个例子算了一下。
假设

问题3

DFS的输出
在这里插入图片描述
贪心法的输出
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值