JAVA程序设计:大礼包(LeetCode:638)

在LeetCode商店中, 有许多在售的物品。

然而,也有一些大礼包,每个大礼包以优惠的价格捆绑销售一组物品。

现给定每个物品的价格,每个大礼包包含物品的清单,以及待购物品清单。请输出确切完成待购清单的最低花费。

每个大礼包的由一个数组中的一组数据描述,最后一个数字代表大礼包的价格,其他数字分别表示内含的其他种类物品的数量。

任意大礼包可无限次购买。

示例 1:

输入: [2,5], [[3,0,5],[1,2,10]], [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:

输入: [2,3,4], [[1,1,0,4],[2,2,1,9]], [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更加便宜。
说明:

最多6种物品, 100种大礼包。
每种物品,你最多只需要购买6个。
你不可以购买超出待购清单的物品,即使更便宜。

思路:

方法一:回溯。设当前搜索到的状态为 shopping(price, special, needs),其中 price 和 special 为题目中所述的物品的单价和捆绑销售的大礼包,而 needs 为当前需要的每种物品的数量。对于此时的 needs,我们可以考虑全部使用原价购买,总价即为 price 与 needs 对应位置相乘的结果;我们也可以使用大礼包,对于 special 中的某一个大礼包 s,如果 s 中的每种物品数量都不大于 needs 中对应物品数量,那么我们就可以使用这个大礼包 s。这样我们会递归地搜索状态 shopping(price, special, needs'),其中 needs' = needs - s,表示更新过的每种物品的数量。在状态 shopping(price, special, needs) 搜索完毕后,会返回其最小花费。

class Solution {
    public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
        return shopping(price,special,needs);
    }
    
    private int shopping(List<Integer> price,List<List<Integer>> special,List<Integer> needs) {
    	int j=0,res=dot(needs,price);
    	for(List<Integer> s:special) {
    		ArrayList<Integer> clone=new ArrayList<>(needs);
    		for(j=0;j<needs.size();j++) {
    			int diff=clone.get(j)-s.get(j);
    			if(diff<0) break;
    			clone.set(j, diff);
    		}
    		if(j==needs.size()) 
    			res=Math.min(res, s.get(j)+shopping(price,special,clone));
    	}
    	return res;
    }
    
    private int dot(List<Integer> needs,List<Integer> price) {
    	int sum=0;
    	for(int i=0;i<needs.size();i++)
    		sum+=needs.get(i)*price.get(i);
    	return sum;
    }
}

方法二:记忆化搜索。我们可以发现,对于搜索状态 shopping(price, special, needs),无论它是从哪个前置状态递归得来的,只要 needs 的值相同(price 和 special 在递归时是不会变的,我们将其放入搜索状态仅仅是为了方便使用这些变量),那么返回的最小花费也相同。因此我们可以使用一个哈希映射(HashMap)存储每个 needs 对应的最小花费,当我们递归进入一个搜索状态时,如果该状态中的 needs 没有出现过,那么我们进行搜索,并在搜索结束时将结果存入哈希映射;如果该状态中的 needs 出现过,那么我们就省去了搜索,直接返回哈希映射中对应的最小花费即可。

class Solution {
	
	private Map<List<Integer>,Integer> map;
	
    public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
    	map=new HashMap<>();
        return shopping(price,special,needs);
    }
    
    private int shopping(List<Integer> price,List<List<Integer>> special,List<Integer> needs) {
    	
    	if(map.containsKey(needs)) return map.get(needs);
    	
    	int j=0,res=dot(needs,price);
    	for(List<Integer> s:special) {
    		ArrayList<Integer> clone=new ArrayList<>(needs);
    		for(j=0;j<needs.size();j++) {
    			int diff=clone.get(j)-s.get(j);
    			if(diff<0) break;
    			clone.set(j, diff);
    		}
    		if(j==needs.size()) 
    			res=Math.min(res, s.get(j)+shopping(price,special,clone));
    	}
    	map.put(needs, res);
    	return res;
    }
    
    private int dot(List<Integer> needs,List<Integer> price) {
    	int sum=0;
    	for(int i=0;i<needs.size();i++)
    		sum+=needs.get(i)*price.get(i);
    	return sum;
    }
}
发布了987 篇原创文章 · 获赞 175 · 访问量 21万+

猜你喜欢

转载自blog.csdn.net/haut_ykc/article/details/103976097