LeetCode:638. Shopping Offers - Python

问题描述:

638. 大礼包

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

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

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

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

任意大礼包可无限次购买

示例 1:

输入: [2,3,4], [[1,1,0,4],[2,2,1,9]], [1,2,1]
输出: 11
解释: A,B,C的价格分别为¥2,¥3,¥4.你可以用¥4购买1A和2B,也可以用¥9购买2A,2B和1C。你需要买1A,2B和1C,所以你付了¥4买了1A和1B(大礼包1),以及¥3购买1B, ¥4购买1C。你不可以购买超出待购清单的物品,尽管购买大礼包2更加便宜。

说明:

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

问题分析:

  这个题目,猛地一看,感觉无法下手,其实,仔细想想,一个深度优先搜索,是可以解决的。现在先明确题目中的一个说明,就是你不可以购买超出待购清单的物品,即使更便宜,这个很重要,可以进行减枝,加快计算。
   在这里使用记忆化搜索(Search Memoization)方法。
  (1)令 dp[cur] = val,表示,在我们需要的商品数量为cur时,的最小花费为val。例如dp[(1,2,1)] = val,表示我们需要的商品数cur = [1,2,1] 时的最小花费为val,其中dp可以使用字典数据类型。
   (2)从上向下,进行递归,如下草图(黑色线表示下递归,红色虚线表示回溯底层最优解),在最原始的状态上,遍历每一个礼包,获取最优值,每个礼包继续下递归,完成深度优先搜索。在这个过程中,每次遍历的初始化状态就是,不使用礼包时的价格。所以递推方程为:
      dp[cur] = min(val, dp.get(tmp, dfs(tmp)) + spec[-1])
tmpcur使用了某一个礼包之后的需要数, spec[-1] 对应这当前礼包的价格。在同一层上遍历一边,获取最小的那个值。
LeetCode:638. Shopping Offers - Python_第1张图片

Python3实现:

# @Time   :2018/7/29
# @Author :LiuYinxing


class Solution:
    def shoppingOffers(self, price, special, needs):

        dp = {}  # 初始化,dp,用于保存每个状态的最优解

        def dfs(cur):
            val = sum(cur[i] * price[i] for i in range(len(needs)))  # 不用礼包时的价格
            for spec in special:
                tmp = [cur[j] - spec[j] for j in range(len(needs))]
                if min(tmp) >= 0:  # 过滤掉,礼包里面的商品多于需求的,礼包, 其中这个一步也相当于减枝
                    val = min(val, dp.get(tuple(tmp), dfs(tmp)) + spec[-1])  # 循环--递归--获取最优解
            dp[tuple(cur)] = val
            return val
        return dfs(needs)


if __name__ == '__main__':
    solu = Solution()
    price, special, needs = [2, 3, 4], [[1, 1, 0, 4], [2, 2, 1, 9]], [1, 2, 1]
    print(solu.shoppingOffers(price, special, needs))

知识补充:

(1)Python 字典数据结构中的,get() 函数返回指定键的值,如果值不在字典中返回默认值。
dict.get(key, default=None) 其中,

  • key – 字典中要查找的键。
  • default – 如果指定键的值不存在时,返回该默认值值。

(2)记忆化搜索(Search Memoization)
算法上依然是搜索的流程,但是搜索到的一些解用动态规划的那种思想和模式作一些保存。一般说来,动态规划总要遍历所有的状态,而搜索可以排除一些无效状态。更重要的是搜索还可以剪枝,可能剪去大量不必要的状态,因此在空间开销上往往比动态规划要低很多。记忆化算法在求解的时候还是按着自顶向下的顺序,但是每求解一个状态,就将它的解保存下来,以后再次遇到这个状态的时候,就不必重新求解了。这种方法综合了搜索和动态规划两方面的优点,因而还是很有实用价值的[1]。

参考链接:

[1] : blog.csdn.net/ilecy/article/details/50867212
欢迎指正哦。

你可能感兴趣的:(算法,Python,LeetCode)