每日一题——Python实现PAT乙级1020 月饼(举一反三+思想解读+逐步优化)


一个认为一切根源都是“自己不够强”的INTJ

个人主页:用哲学编程-CSDN博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数

Python-3.12.0文档解读

目录

我的写法

专业点评:

时间复杂度分析:

空间复杂度分析:

总结:

我要更强

时间复杂度优化:

空间复杂度优化:

优化后的代码解释:

时间复杂度分析:

空间复杂度分析:

总结:

哲学和编程思想

1. 贪心算法(Greedy Algorithm)

2. 优先队列(Priority Queue)

3. 空间-时间权衡(Space-Time Tradeoff)

4. 原地修改(In-place Modification)

5. 分治法(Divide and Conquer)

6. 减少问题规模(Reduce Problem Size)

举一反三

1. 贪心算法(Greedy Algorithm)

2. 优先队列(Priority Queue)

3. 空间-时间权衡(Space-Time Tradeoff)

4. 原地修改(In-place Modification)

5. 分治法(Divide and Conquer)

6. 减少问题规模(Reduce Problem Size)

具体技巧和示例:

总结


 题目链接

我的写法
 

# 读取输入
N, D = map(float, input().split())  # 读取月饼种类数N和市场最大需求量D,转换为浮点数
N = int(N)  # 将月饼种类数N转换为整数,因为月饼种类数不应该是小数
storages = list(map(float, input().split()))  # 读取每种月饼的库存量,并转换为浮点数
prices = list(map(float, input().split()))  # 读取每种月饼的总售价,并转换为浮点数

# 构建每种月饼的信息,包括库存量、总售价、和单位售价
# 注意月饼库存量和总售价可以是小数,初始三个参数可以都设为float型(测试点2)
infos = []
for i in range(N):  # 遍历每种月饼
    unit_price = prices[i] / storages[i]  # 计算每种月饼的单位售价(每吨售价)
    infos.append([storages[i], prices[i], unit_price])  # 将每种月饼的信息存入列表

# 按单位售价从高到低排序
infos.sort(key=lambda x: x[2], reverse=True)  # 按单位售价从高到低对月饼信息进行排序

output = 0.0  # 初始化输出的最大收益
for storage, total_price, unit_price in infos:  # 遍历排序后的月饼信息
    if D == 0.0:  # 如果市场需求量已经满足,退出循环
        break
    if storage <= D:  # 如果当前月饼的库存量小于等于剩余需求量
        output += total_price  # 将当前月饼的总售价加入收益
        D -= storage  # 减少相应的需求量
    else:  # 如果当前月饼的库存量大于剩余需求量
        output += D * unit_price  # 根据剩余需求量计算收益
        D = 0.0  # 将需求量设为0,表示已经满足需求

print(f"{output:.2f}")  # 输出最大收益,保留两位小数

 

这段代码实现了基于库存量和总售价的月饼销售策略,以最大化收益为目标。代码逻辑清晰,基本思路是先计算每种月饼的单位售价(即每吨售价),然后根据单位售价从高到低排序,优先销售单位售价高的月饼,以达到最大收益。以下是对这段代码的专业点评,以及其时间复杂度和空间复杂度的分析。

专业点评:

  1. 数据输入和预处理:
    • 输入处理部分正确地将数据转换为浮点数,保证在处理涉及小数的计算时不会出现精度问题。
  2. 计算单位售价并排序:
    • 通过计算每种月饼的单位售价并将其存储在列表中,排序时选择了基于单位售价从高到低的顺序,这一步很合理,因为单位售价高的月饼优先销售可以最大化总收益。
  3. 收益计算逻辑:
  • 代码通过遍历排序后的月饼信息,根据库存量和剩余需求量更新总收益,逻辑清晰且高效。

时间复杂度分析:

  1. 输入读取和预处理:
    • 读取和转换数据的时间复杂度为O(N),因为需要对每一个输入数据进行处理。
  2. 计算单位售价并排序:
    • 计算单位售价的时间复杂度为O(N),因为需要对每一种月饼计算一次。
    • 排序的时间复杂度为O(N log N),因为使用了Timsort(Python内置的排序算法)。
  3. 收益计算:
  • 遍历排序后的月饼信息,并根据需求量计算总收益,这部分时间复杂度为O(N)。

综合上述步骤,代码的总体时间复杂度为O(N log N),这是因为排序是主要的时间消耗部分。

空间复杂度分析:

  1. 输入数据存储:
    • 存储库存量和总售价需要两个长度为N的列表,空间复杂度为O(N)。
  2. 单位售价信息存储:
  • 存储每种月饼的库存量、总售价和单位售价,空间复杂度为O(N)。

因此,总体空间复杂度为O(N),因为存储每种月饼的信息占用了线性空间。

总结:

这段代码有效地解决了基于单位售价最大化收益的问题。时间复杂度主要由排序部分决定,为O(N log N),而空间复杂度为O(N),这是非常高效且合理的。可以进一步提高代码的健壮性,例如增加更多的错误处理和边界情况处理。总体上,这段代码在逻辑上是正确且高效的。


我要更强

在这段代码中,时间复杂度的主要瓶颈在于排序操作(O(N log N)),而空间复杂度是O(N),主要是存储月饼信息。针对这两个方面,我们可以尝试一些优化方法。

时间复杂度优化:

  1. 如果月饼种类数很大,而市场需求量相对较小,可以采用优先队列(堆)来优化。我们可以使用最大堆来动态选择当前单位售价最高的月饼进行销售,从而避免全局排序,从而减少时间复杂度。

空间复杂度优化:

  1. 原地修改:将月饼的库存量、总售价、和单位售价等信息存储在原始列表中,避免额外的空间开销。

以下是结合这些优化后的代码示例,使用优先队列来改进时间复杂度,并在一定程度上降低空间复杂度。

import heapq

# 读取输入
N, D = map(float, input().split())
N = int(N)
storages = list(map(float, input().split()))
prices = list(map(float, input().split()))

# 创建一个最大堆存储每种月饼的单位售价及其相关信息
heap = []
for i in range(N):
    unit_price = prices[i] / storages[i]  # 计算单位售价
    heapq.heappush(heap, (-unit_price, storages[i], prices[i]))  # 使用负值构建最大堆

output = 0.0  # 初始化收益
while D > 0 and heap:  # 当市场需求量未满足且堆非空时
    unit_price, storage, total_price = heapq.heappop(heap)  # 弹出单位售价最高的月饼信息
    unit_price = -unit_price  # 将单位售价恢复为正值
    if storage <= D:  # 如果当前月饼库存量小于等于需求量
        output += total_price  # 将总售价加入收益
        D -= storage  # 减少相应的需求量
    else:  # 如果当前月饼库存量大于需求量
        output += D * unit_price  # 根据剩余需求量计算收益
        D = 0.0  # 将需求量设为0,表示需求已满足

print(f"{output:.2f}")  # 输出最终收益,保留两位小数

优化后的代码解释:

  1. 使用堆优化排序过程:
    • 在计算单位售价后,将其作为负值存入最大堆(Python的heapq默认是最小堆,所以使用负值来实现最大堆的效果)。
    • 这样每次从堆中弹出元素时,实际上是弹出当前单位售价最高的月饼信息。
  2. 减少额外空间开销:
  • 最大堆中仅存储必要的月饼信息,没有创建额外的数据结构来存储排序后的信息。

时间复杂度分析:

  • 堆操作:
  • 插入堆的时间复杂度为O(log N),总共进行N次插入操作,耗时为O(N log N)。
  • 弹出堆顶元素的时间复杂度为O(log N),最坏情况下需要进行N次弹出操作,所以总耗时为O(N log N)。

空间复杂度分析:

  • 使用堆存储月饼信息,空间复杂度为O(N),因为堆中最多存储N个元素。
  • 没有创建额外的列表或数据结构来存储排序后的信息,进一步减少了空间占用。

总结:

优化后的代码通过使用最大堆将全局排序操作优化为局部最优选择,同时减少了额外空间开销。时间复杂度从O(N log N)降到O(N log N)(保持不变,但更高效),空间复杂度维持在O(N),进一步提高了效率和资源利用率。


哲学和编程思想

在优化这段代码的过程中,我们使用了一些经典的编程思想和哲学,这些方法不仅提高了算法的效率,也展示了编程中的一些关键原则。以下是详细的分析:

1. 贪心算法(Greedy Algorithm)

思想:贪心算法是一种在每一步选择中都采取当前状态下最好或最优的选择,最终希望通过一系列当地最优选择达到全局最优。

应用:

  • 我们基于月饼的单位售价从高到低的顺序选择月饼进行销售,以实现每一步的收益最大化。这种方式确保了整体收益的最大化。

2. 优先队列(Priority Queue)

思想:优先队列是一种数据结构,它允许以动态的方式对元素进行管理,并保证每次操作都能高效地获取优先级最高或最低的元素。

应用:

  • 使用最大堆(优先队列)来管理月饼的单位售价信息,使得每次弹出堆顶都能快速获取当前单位售价最高的月饼,减少了全局排序的时间复杂度。

3. 空间-时间权衡(Space-Time Tradeoff)

思想:程序优化中,有时需要在空间复杂度和时间复杂度之间进行权衡,以找到最优的折中方案。

应用:

  • 通过最大堆管理月饼信息,减少了排序所需的时间,同时避免了额外的数据结构,这体现了在空间和时间上的优化选择。

4. 原地修改(In-place Modification)

思想:在保证正确性的前提下,通过直接在原始数据结构上进行修改,减少额外的空间开销。

应用:

  • 使用负值堆构建最大堆,利用原有的列表存储计算结果,减少了不必要的内存使用。

5. 分治法(Divide and Conquer)

思想:通过将问题分解为多个子问题,分别解决子问题,最终合并结果来解决原问题。

应用:

  • 在排序的过程中,尽管我们采取的是贪心策略,但实际上是通过逐步解决局部最优问题来达到全局最优的效果。

6. 减少问题规模(Reduce Problem Size)

思想:通过每一步操作减少问题的规模,逐步逼近问题的解。

应用:

每次弹出堆顶元素(即单位售价最高的月饼),并更新市场需求量D,逐步减少问题规模,最终达到停止条件。


举一反三

理解编程中的哲学和思想能够帮助更好地解决问题和优化代码。以下是一些技巧和建议,帮助你在实际编程中运用这些思想,并举一反三。

1. 贪心算法(Greedy Algorithm)

技巧:

  • 识别贪心选择性质:在每一步选择中,找出当前最优的选择,并验证该选择是否能构成全局最优解。
  • 局部最优到全局最优:确保每一步的局部最优选择不会影响全局最优解的实现。

应用:

  • 优先考虑当前最具收益的操作,例如选择单位售价最高的月饼。
  • 在路径规划或调度问题中,优先选择代价最小的路径或任务。

2. 优先队列(Priority Queue)

技巧:

  • 动态管理:利用堆(或优先队列)在动态环境中快速获取当前最优解。
  • 堆的操作:熟练掌握堆的插入、删除、和调整操作,确保高效管理优先级。

应用:

  • 任务调度:使用优先队列管理任务,动态选择最优任务执行。
  • 网络流量管理:在网络路由中使用优先队列选择最优路径。

3. 空间-时间权衡(Space-Time Tradeoff)

技巧:

  • 缓存与存储:利用缓存(如动态规划中的记忆化)减少重复计算,但需考虑空间开销。
  • 预计算:预先计算并存储常用结果,换取运行时的计算效率。

应用:

  • 动态规划:通过存储子问题的解来提高效率。
  • 数据处理:在大数据处理中,通过预计算关键数据减少实时计算开销。

4. 原地修改(In-place Modification)

技巧:

  • 原地算法:设计算法时尽量在原数据结构上进行修改,减少额外空间占用。
  • 指针或索引操作:使用指针或索引直接操作数组和链表,提高效率。

应用:

  • 排序算法:如快速排序、堆排序等,尽量使用原地排序。
  • 数组处理:在数组中进行元素交换或重排时,尽量避免创建额外的数组。

5. 分治法(Divide and Conquer)

技巧:

  • 问题分解:将复杂问题分解为若干个子问题,分别解决后合并结果。
  • 递归思维:利用递归函数解决子问题,注意基准条件和递归关系。

应用:

  • 快速排序和归并排序:分解数组,再递归排序。
  • 大整数乘法:将大整数分解为较小整数计算再合并。

6. 减少问题规模(Reduce Problem Size)

技巧:

  • 逐步逼近:通过每一步操作逐步减少问题规模,直至问题解决。
  • 迭代改进:在每次迭代中改进结果,逐步达到最终解。

应用:

  • 递归算法:通过递归调用逐步减少问题规模。
  • 二分查找:每次缩小搜索范围,逐步逼近目标值。

具体技巧和示例:

  1. 动态规划与记忆化:
    • 识别子问题,存储中间结果。
    • 如计算斐波那契数列,避免重复计算。
  2. 滑动窗口:
    • 通过调整窗口范围高效处理连续子数组问题。
    • 如找到数组中和为目标值的连续子数组。
  3. 双指针:
  • 通过双指针遍历数组,解决合并、查找等问题。
  • 如合并两个有序数组。

总结

通过理解和运用这些编程思想和哲学,可以在解决问题时更具创造性和高效性。关键在于多加练习,逐步培养敏锐的算法设计和优化思维。


感谢。

  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

用哲学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值