leetcode 638. 大礼包-思路整理

news2024/11/26 15:31:08

题目

在 LeetCode 商店中, 有n件在售的物品。每件物品都有对应的价格。然而,也有一些大礼包,每个大礼包以优惠的价格捆绑销售一组物品。
给你一个整数数组price表示物品价格,其中price[i]是第i件物品的价格。另有一个整数数组needs表示购物清单,其中needs[i]是需要购买第i件物品的数量。还有一个数组special表示大礼包,special[i]的长度为n + 1,其中special[i][j]表示第i个大礼包中内含第j件物品的数量,且special[i][n](也就是数组中的最后一个整数)为第i个大礼包的价格。
返回 确切 满足购物清单所需花费的最低价格,你可以充分利用大礼包的优惠活动。你不能购买超出购物清单指定数量的物品,即使那样会降低整体价格。任意大礼包可无限次购买。

示例

输入: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 。

题解

该问题就跟平时我们去商场购物一样,大礼包就是多种商品凑一起的打折方式。这里以上题示例来进行说明,price=[2,5]表示有两个商品A与B
其中A商品的单价是2,B商品的单价是5。special就是所谓的大礼包,special = [[3,0,5],[1,2,10]],表示其一共有两个大礼包或是打折方式,一种是[3,0,5]表示A商品3个B商品0个的打折价是5,这明显比直接买3个A商品6的价格要便宜。[1,2,10]表示买1个A商品2个B商品的打折价是10显然要比单独买1个A商品2个B商品要便宜。needs=[3,2]中需要购买的商品数,其中需要A商品3个B商品5个。这里的问题是购买购物清单里的商品最少花费多少。这里有一个限制是所有商品的数量不能超过购物清单的数量,即使打折的价位很便宜。

思考过程

我比较喜欢通过人为的判断过程来整理思路。因为题目给出的special并一定都比单独买便宜,因为需要确定有效的special。示例中的两个special显然要比单独买便宜,因此均有效。

在这里插入图片描述

如上图所示为初始状态,需要的购物表为needs列表。其中的选择策略要么是大礼包,要么是单买。那么接下来我们如何选择呢,如果大礼包有效的话,那么肯定首选大礼包。
在这里插入图片描述

大礼包1明显满足要求,在选择大礼包1后,购物列表减少变为[0,2],当前花费了大礼包1的价格5。我们也发现其实也可以选择大礼包2。
在这里插入图片描述

选择大礼包2的情况如上图所示。但是到这里我们没办法判断是选择大礼包1好些还是选择大礼包2好些。这里还有一个单买为什么不一起考虑,之前讨论过,有效的大礼包是肯定要比单买便宜的,因此有大礼包可选择就不需要考虑单买。

在这里插入图片描述

继续分别考虑,由于需要购物的列没有满足要求的大礼包因此只能单买,这两种情况的价位分别是15与14。到此,其实思路已经很明确了。整个过程改变的量只有需要的购物表,求得的最小的花费。由于购物表的状态变化是可以自底向上的,我一开始考虑的是使用动态规划。但是在写代码的过程中发现dp中的维度是一个变量,也就是购物表中的物品是一个不确定量,这导致无法自底向上的构建状态矩阵。我个人一直认为动态规划=递归+记忆化搜索。因此,这里直接考虑使用递归+记忆化搜索。
递归的思路:

  • 输入:需要的购物表
  • 输出:最小花费
  • 过程:首先是边界判断,最坏的情况就是所有物品都单买,这里作为上界。遍历每个大礼包,考虑选择该大礼包的花费

初始代码:

class Solution:
    def shoppingOffers(self, price: List[int], special: List[List[int]], needs: List[int]) -> int:
        #过滤掉无用的大礼包
        usefull_special=[]
        for sub_spe in special:
            spe_price=sum([item[0]*item[1] for item in list(zip(sub_spe[:-1],price))])
            print(spe_price,sub_spe[-1])
            if spe_price>sub_spe[-1]:
                print(sub_spe)
                usefull_special.append(sub_spe)

        #递归函数dfs(need_list)返回need_list的最小
        def dfs(needs_list):
            # 不购买任何大礼包人情况下,所有物品单独买是购物价格的上限
            min_price = sum(need * price[i] for i, need in enumerate(needs_list))
            #终止条件所有needs_list均为0
            if all(item==0 for item in needs_list):
                return min_price
            #分情况讨论,首先是判断能使用大礼包的情况
            for special in usefull_special:
                #判断大礼包是否可使用
                curr_needs=[needs_list[i]-special[i] for i in range(len(needs_list))]
                if all(item>=0 for item in curr_needs):
                    min_price=min(min_price,special[-1]+dfs(curr_needs))
            return min_price
        return dfs(needs)

优化

可以使用装饰器 @lru_cache(None) 实现记忆化搜索,由于记忆化搜索不支持list输入,把list转换成tuple。

代码如下:

class Solution:
    def shoppingOffers(self, price: List[int], special: List[List[int]], needs: List[int]) -> int:
        #过滤掉无用的大礼包
        usefull_special=[]
        for sub_spe in special:
            spe_price=sum([item[0]*item[1] for item in list(zip(sub_spe[:-1],price))])
            print(spe_price,sub_spe[-1])
            if spe_price>sub_spe[-1]:
                print(sub_spe)
                usefull_special.append(sub_spe)

        #递归函数dfs(need_list)返回need_list的最小
        @lru_cache(None)
        def dfs(needs_list):
            # 不购买任何大礼包人情况下,所有物品单独买是购物价格的上限
            min_price = sum(need * price[i] for i, need in enumerate(needs_list))
            #终止条件所有needs_list均为0
            #if all(item==0 for item in needs_list):
                #return min_price
            #分情况讨论,首先是判断能使用大礼包的情况
            for special in usefull_special:
                #判断大礼包是否可使用
                curr_needs=[needs_list[i]-special[i] for i in range(len(needs_list))]
                if all(item>=0 for item in curr_needs):
                    min_price=min(min_price,special[-1]+dfs(tuple(curr_needs)))
            return min_price
        return dfs(tuple(needs))

复杂度分析

  • 时间复杂度: O ( n × k × m n ) O(n×k×m^n) O(n×k×mn) ,其中 k k k 表示大礼包的数量, m m m 表示每种物品的需求量的可能情况数(等于最大需求量加 1), n n n 表示物品数量。我们最多需要处理 m n m^n mn个状态,每个状态需要遍历 k k k 种大礼包的情况,每个大礼包需要遍历 n n n 种商品以检查大礼包是否可以购买。
  • 空间复杂度: O ( n × m n ) O(n×m^n) O(n×mn),用于存储记忆化搜索中 m n m^n mn个状态,每个状态需要存储n个商品的需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/68223.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

扩散模型(Diffusion)最新综述+GitHub论文汇总-A Survey On Generative Diffusion

扩散模型(Diffusion Model)最新综述GitHub论文汇总-A Survey On Generative Diffusion 本综述来自香港中文大学Pheng-Ann Heng、西湖大学李子青实验室和浙江大学陈广勇团队,对现有的扩散生成模型进行了全面的回顾。本文首先提出了diffusion model改进算法的细化分类…

Thread类及常见方法

文章目录一、Thread常见构造方法二、Thread常见属性三、Thread常见方法start()获取当前线程休眠当前线程中断线程join()一、Thread常见构造方法 Thread类是JVM用来管理线程的一个类,每个线程都有唯一一个Thread对象与之对应,JVM会将这些对象组织起来&am…

世界杯征文活动 | 神奇!一段JavaScript代码生成会动的足球

世界杯征文活动 | 神奇!一段JavaScript代码生成会动的足球 文章目录前言一、效果展示二、代码解析1.把足球图片转换为base64格式2.根据base64格式的字符串,创建img标签图片对象3.创建存放图片img的div标签对象4.使div旋转起来总结前言 花有重开日&#…

python中的import详解

0. 什么是导入? 导入从本质上讲,就是载入另一个文件,并能够读取那个文件的内容 0.1 模块和属性 模块往往就是变量名的封装,被认作是命名空间属性就是绑定在特定对象上的变量名 0.2 from和import 通过import得到了具有属性的模…

697226-52-1, 细胞穿膜肽TAT-amide

TAT-amide 是一种细胞穿透肽。细胞穿透肽 (CPPs) 是能够进入不同细胞的短氨基酸序列,用于细胞的物质细胞递送。TAT-amide is a cell penetrating peptide. Cell-penetrating peptides (CPPs) are short amino acid sequences able to enter different cells[1][2]. …

软件测试工程师应该学Python还是学Java?

前言 对于一个软件测试工程师来说,选哪一门语言来入手编程一直是件非常纠结的事情,当然立志做一辈子功能测试的人除外。 当你学完软件测试基本理论,掌握业务测试流程,功能测试可以搞定,数据库和linux玩的也很溜时&am…

Day822.Happens-Before 规则 -Java 并发编程实战

Happens-Before 规则 Hi,我是阿昌,今天学习记录的是关于Happens-Before 规则的内容。 可见性、原子性、有序性这三者在编程领域属于共性问题,所有的编程语言都会遇到,Java 在诞生之初就支持多线程,自然也有针对这三者…

Blender 雕刻

文章目录简介.基本操作.进入雕刻.雕刻工作区.动态拓扑.动态拓扑属性.重构网格.物体数据属性重构网格.雕刻自带的重构网格.简介. 1 雕刻的本质是是用笔刷对顶点进行移动 2 被雕刻的物体要有足够多的顶点才行,可以使用动态拓扑解决顶点不够的问题 基本操作. 进入雕…

你猜,怎么用一句话证明你是项目经理?

早上好,我是老原。 熟悉我的老朋友都知道,我有两大爱好,一是钟情于挖招聘JD,二是喜欢逛知乎。 今天在知乎上看到一个很有趣的话题:你如何用一句话证明你是项目经理? 项目管理发展至今,有技术/…

JavaScript大作业(餐厅美食网站设计与实现)

🎀 精彩专栏推荐👇🏻👇🏻👇🏻 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业…

【CBAM||目标识别||注意力机制||gated卷积】Convolutional Block Attention Module

这篇是2018年的paper,已经有很多中文资料可以学习,因而不做重复工作~记录一下核心要点,后续可阅。【学习资源】CBAM:卷积注意力机制模块 【学习资源】论文阅读-CBAM: Convolutional Block Attention Module CBMA:卷积注意力机制模…

10K起步的软件测试岗到底需要学什么?零基础进阶自动化测试需要哪些技术...

软件测试的正确路线 1、软件测试基础: 对软件测试整理的测试流程有清晰的概念,了解软件测试到底是做什么的,软件测试的各种专业术语是什么意思,以及多种不同的软件测试类型区分,测试用例的作用、本质以及如何撰写&…

开关电源环路稳定性分析(2)-从开环到闭环

大家好,这里是大话硬件。 在上一节中,基于欧姆定律,基尔霍夫定律,伏秒平衡这些已知的知识点,可以推导出Buck变换器的输入输出关系。 今天这一节,我们还是从全局的概念来解析开关电源。 1. 运放和开关电源…

盘点世界杯有趣小知识!带你感受体育赛事数据可视化的快乐!

2022年卡塔尔世界杯是第二十二届世界杯足球赛,是历史上首次在卡塔尔和中东国家境内举行、也是第二次在亚洲举行的世界杯足球赛。 今年卡塔尔世界杯可谓精彩纷呈,花2000个亿在沙漠里打造出的空调球场、洗脑又魔性的“母鸡生蛋”主题曲《Tukoh Taka》、世界…

角逐「视觉感知」万亿市场,这家国内领跑者如何挑战性能天花板?

随着智能汽车渗透率快速提升,车用视觉感知摄像头装配量大幅增长。以前向ADAS摄像头为例,今年1-8月中国市场(不含进出口)乘用车新车标配交付为639.10万颗,同比增长29.54%;而这个数字在2021年同期为55.67%。 …

ppt 的基本操作1

一 基本操作 1.1 隐藏和显示功能区 1.点击有上角,小箭头标志 2.显示 1.2 工作区 和编辑区的比例拖放 1.1 箭头放到红色标注的部分,当鼠标变为箭头形状,可以动态拖动 2.可以看到二者之间的比例,发生变化 1.3 编辑区设置网格线…

Minianaconda安装jupyter notebook遇到的问题及解决

文章目录前言一、如何安装jupyter notebook二、其他问题解决1、安装时报错2、安装之后不能打开3、Verifying transaction: failedRemoveError注意:使用时命令提示符窗口不可以关闭前言 提示:这里可以总结遇到的各种问题: 1、如何安装jupyte…

Excel 可视化教程之可视化的科学与艺术

我们经常对表示数据的方式感到不知所措,所以这里是关于为什么要进行可视化以及在进行可视化时的基本思考过程。 探索性与解释性 原始格式的数据既不美观也不具有洞察力。为了掌握潜在的分布、异常和洞察力,我们需要进行探索性数据分析,通常称为 EDA。因此,数据的探索部分…

MySQL表的操作

文章目录MySQL表的操作创建表创建表案例查看表结构修改表删除表MySQL表的操作 表操作至少会涉及如下两类SQL语句: DDL(Data Definition Language)数据定义语言:比如建表、删表、该表、新增列、删除列等。DML(Data Ma…

电容笔有什么用?电容笔10大品牌排行榜

当电容笔与ipad配合使用时,将会极大地提高我们的工作以及学习效率,同时增加更多的乐趣,而不会让人觉得枯燥。在画画方面,电容笔的重要作用不可忽略。我对电容笔还是很了解的,很多电容笔都是适用于我们的ipad的&#xf…