题目解析:1705. 吃苹果的最大数目
问题描述
你有一棵特殊的苹果树,连续 n 天,每天树上可能会长出若干个苹果。对于第 i 天:
apples[i]
表示当天长出的苹果数量。- 这些苹果将在
days[i]
天后腐烂,也就是说,这些苹果在第i + days[i]
天时就无法食用了。
你计划每天最多吃一个苹果,以保证营养均衡。需要注意的是,即使在 n 天之后,你仍然可以继续吃苹果,直到所有苹果都腐烂。
目标: 返回你最多可以吃掉的苹果数量。
示例解析
示例 1
输入:apples = [1,2,3,5,2], days = [3,2,1,4,2]
输出:7
解释:
- 第1天,树上有1个苹果,3天后腐烂(第4天)。
- 第2天,树上有2个苹果,2天后腐烂(第4天)。
- 第3天,树上有3个苹果,1天后腐烂(第4天)。
- 第4天,树上有5个苹果,4天后腐烂(第8天)。
- 第5天,树上有2个苹果,2天后腐烂(第7天)。
你可以按照如下方式吃苹果:
- 第1天,吃第1天长出的1个苹果。
- 第2天,吃第2天长出的2个苹果中的一个。
- 第3天,吃第2天长出的另一个苹果(第3天苹果会腐烂)。
- 第4天到第7天,你吃的都是第4天长出的苹果。
总共吃掉7个苹果。
示例 2
输入:apples = [3,0,0,0,0,2], days = [3,0,0,0,0,2]
输出:5
解释:
- 第1天到第3天,吃的都是第1天长出的苹果。
- 第4天和第5天不吃苹果。
- 第6天和第7天,吃的都是第6天长出的苹果。
总共吃掉5个苹果。
解题思路
为了最大化吃掉的苹果数量,我们需要合理安排每天吃哪个苹果,以避免苹果腐烂。由于苹果有保质期(即腐烂时间),需要优先吃那些即将腐烂的苹果。
步骤详解
-
数据结构选择:
- 使用**最小堆(Min Heap)**来管理苹果,堆中存储的是每批苹果的过期时间和数量。这样,堆顶元素始终是最早过期的一批苹果,便于优先处理。
-
处理流程:
- 遍历每一天,处理当天生成的苹果。
- 移除堆中已经腐烂的苹果。
- 添加当天新生成的苹果到堆中。
- 吃掉堆顶的苹果(即最早腐烂的苹果)。
- 统计总共吃掉的苹果数量。
- 继续循环,直到所有生成的苹果都被处理完毕,即使超过n天。
-
循环条件:
- 循环继续的条件是当前天数
i
还在生成苹果的范围内,或者堆中还有未腐烂的苹果。即使用while i < len(apples) or heap:
。
- 循环继续的条件是当前天数
关键点
- 优先吃即将腐烂的苹果,避免浪费。
- 堆的使用确保每次取出的都是最早过期的苹果。
- 继续循环直到所有苹果都被处理完毕,包括n天之后的苹果。
代码实现
以下是完整的 Python 代码,并附带详细解释:
import heapq
from typing import List
class Solution:
def eatenApples(self, apples: List[int], days: List[int]) -> int:
ans = i = 0
h = []
while i < len(apples) or h:
# 移除已经腐烂的苹果
while h and h[0][0] <= i:
heapq.heappop(h)
# 如果当天有新苹果生成,加入堆中
if i < len(apples) and apples[i] > 0:
heapq.heappush(h, [i + days[i], apples[i]])
# 吃掉最早腐烂的苹果
if h:
ans += 1
h[0][1] -= 1 # 吃掉一个苹果
if h[0][1] == 0:
heapq.heappop(h)
i += 1 # 进入下一天
return ans
代码详解
-
导入必要的模块:
import heapq from typing import List
heapq
用于实现最小堆。List
是类型提示的一部分,帮助代码更易读。
-
初始化变量:
ans = i = 0 h = []
ans
用于统计总共吃掉的苹果数量。i
表示当前天数,从0开始。h
是一个空的最小堆,用于存储苹果的过期时间和数量。
-
主循环:
while i < len(apples) or h:
- 条件解释:
i < len(apples)
:确保在所有生成苹果的天数内,处理每天生成的苹果。or h
:确保即使在所有天数之后,仍然有未腐烂的苹果时,继续处理堆中的苹果。
- 条件解释:
-
移除已腐烂的苹果:
while h and h[0][0] <= i: heapq.heappop(h)
- 检查堆顶的苹果是否已经腐烂(即过期时间
<=
当前天数)。 - 如果是,移出堆顶元素,直到堆顶的苹果都是未腐烂的。
- 检查堆顶的苹果是否已经腐烂(即过期时间
-
添加当天新生成的苹果:
if i < len(apples) and apples[i] > 0: heapq.heappush(h, [i + days[i], apples[i]])
- 如果当前天数
i
在苹果生成的天数范围内,并且当天有苹果生成 (apples[i] > 0
),则计算这些苹果的过期时间i + days[i]
。 - 将
[i + days[i], apples[i]]
作为一个列表加入堆中。
- 如果当前天数
-
吃掉苹果:
if h: ans += 1 h[0][1] -= 1 # 吃掉一个苹果 if h[0][1] == 0: heapq.heappop(h)
- 如果堆中还有未腐烂的苹果,取出堆顶元素
[过期时间, 剩余数量]
。 - 吃掉一个苹果,
ans
增加1,剩余数量
减少1。 - 如果这一批苹果已经被全部吃完(
剩余数量 == 0
),将其移出堆中。
- 如果堆中还有未腐烂的苹果,取出堆顶元素
-
进入下一天:
i += 1
- 增加
i
,进入下一天的循环。
- 增加
-
返回结果:
return ans
- 循环结束后,返回总共吃掉的苹果数量。
堆的自动排序机制
-
最小堆性质:
- 使用
heapq
实现的最小堆保证堆顶元素始终是最小的。 - 堆中存储的是
[过期时间, 苹果数量]
,所以堆顶元素是过期时间最早的一批苹果。
- 使用
-
自动排序:
- 每次将新苹果
[i + days[i], apples[i]]
推入堆中后,heapq
会自动维护堆的排序。 - 这样,堆顶始终是过期时间最早的苹果,确保优先吃掉即将腐烂的苹果。
- 每次将新苹果
为什么需要 or h
?
-
处理生成后仍可食用的苹果:
- 在前 n 天内,苹果会不断生成并加入堆中,每批苹果有其各自的保质期。
- 一些苹果的保质期可能会超过 n 天。如果不使用
or h
,一旦i
达到 n,循环就会停止,即使堆中还有未腐烂的苹果,无法被吃掉,导致浪费。
-
最大化吃掉的苹果数量:
- 通过继续循环,只要堆中还有未腐烂的苹果,就可以继续吃苹果。这确保了你在前 n 天之外,仍然可以吃到那些在 n 天后仍未腐烂的苹果。
具体示例说明
考虑以下示例:
apples = [2, 1, 1, 0, 0, 0]
days = [5, 3, 2, 0, 0, 0]
天数和堆的变化:
-
第0天(i=0):
- 生成2个苹果,过期时间为
0 + 5 = 5
。 - 堆中有
[[5, 2]]
。 - 吃掉一个苹果,堆中剩余
[[5, 1]]
。
- 生成2个苹果,过期时间为
-
第1天(i=1):
- 生成1个苹果,过期时间为
1 + 3 = 4
。 - 堆中有
[[4, 1], [5, 1]]
。 - 吃掉过期时间最早的苹果(过期时间4),堆中剩余
[[5, 1]]
。
- 生成1个苹果,过期时间为
-
第2天(i=2):
- 生成1个苹果,过期时间为
2 + 2 = 4
。 - 堆中有
[[4, 1], [5, 1]]
。 - 吃掉过期时间4的苹果,堆中剩余
[[5, 1]]
。
- 生成1个苹果,过期时间为
-
第3天(i=3):
- 没有新苹果生成。
- 检查堆中苹果是否过期:
4 <= 3
不成立,堆中仍有[[5, 1]]
。
- 吃掉堆顶的苹果,堆中剩余
[[5, 0]]
,移出堆中。
-
第4天(i=4)和第5天(i=5):
- 没有新苹果生成。
- 堆中没有未腐烂的苹果,循环结束。
如果不使用 or h
:
- 当
i
达到6
时,循环会停止,无法继续处理堆中的苹果,导致部分苹果未被吃掉。
时间与空间复杂度分析
-
时间复杂度: O(n log n)
- 每天可能需要进行一次
heappush
或heappop
操作,堆的操作时间复杂度为 O(log n)。 - 因此,总体时间复杂度为 O(n log n)。
- 每天可能需要进行一次
-
空间复杂度: O(n)
- 堆中可能会存储所有未腐烂的苹果,最坏情况下需要 O(n) 的空间。
总结
通过使用最小堆,我们能够高效地管理苹果的过期时间,确保每次都优先吃掉即将腐烂的苹果,从而最大化吃掉的苹果数量。关键在于合理使用堆的性质,确保在所有天数内及之后都能处理好所有苹果,避免浪费。
希望通过以上详细的解析,你能够更好地理解这道题目以及对应的解题代码。如果还有疑问,欢迎继续讨论!
参考代码
以下是完整的 Python 代码实现:
import heapq
from typing import List
class Solution:
def eatenApples(self, apples: List[int], days: List[int]) -> int:
ans = i = 0
h = []
while i < len(apples) or h:
# 移除已经腐烂的苹果
while h and h[0][0] <= i:
heapq.heappop(h)
# 如果当天有新苹果生成,加入堆中
if i < len(apples) and apples[i] > 0:
heapq.heappush(h, [i + days[i], apples[i]])
# 吃掉最早腐烂的苹果
if h:
ans += 1
h[0][1] -= 1 # 吃掉一个苹果
if h[0][1] == 0:
heapq.heappop(h)
i += 1 # 进入下一天
return ans
常见问题解答
1. 为什么使用最小堆来管理苹果?
使用最小堆的主要原因是我们需要优先处理即将腐烂的苹果。最小堆保证堆顶元素是最早过期的一批苹果,这样每次吃苹果时都能优先选择即将腐烂的苹果,最大限度地避免浪费。
2. 为什么循环条件中需要 or h
?
or h
确保即使在所有生成苹果的天数结束后,仍然能够继续处理堆中的剩余苹果。这样可以确保在 n 天之后仍然可以吃掉那些在 n 天后仍未腐烂的苹果,从而最大化吃掉的苹果数量。
3. 堆中存储的 [i + days[i], apples[i]]
的含义是什么?
i + days[i]
表示这些苹果的过期时间,即第i + days[i]
天时,这些苹果将会腐烂。apples[i]
表示当天生成的苹果数量。
这样,堆中存储的每个元素都是一批苹果的过期时间和数量,便于管理和优先处理。
4. 如何确保堆中的苹果都是未腐烂的?
在每一天开始时,通过以下代码段移除已经腐烂的苹果:
while h and h[0][0] <= i:
heapq.heappop(h)
这段代码检查堆顶的苹果是否已经腐烂(即过期时间 <=
当前天数),如果是,则将其移出堆中。这样,堆中始终只保留未腐烂的苹果。
5. 如何处理当天没有新苹果生成的情况?
如果当天没有新苹果生成,apples[i]
为0,条件 if i < len(apples) and apples[i] > 0:
不满足,因此不会将新苹果加入堆中。但如果堆中还有未腐烂的苹果,仍然会继续吃苹果,直到所有苹果都被吃掉或腐烂。
总结
通过合理利用最小堆的特性,我们能够高效地管理苹果的过期时间,确保每次都优先吃掉即将腐烂的苹果,从而最大化吃掉的苹果数量。这道题考察了对优先队列(堆)的应用,以及如何在有限条件下优化资源的使用。掌握了这种思路和技巧,不仅能解决类似的问题,还能提升对数据结构的理解和应用能力。
进一步阅读
- Python
heapq
模块官方文档 - 优先队列的实现与应用
代码运行示例
以下是一个完整的代码运行示例,帮助你更好地理解代码的执行过程:
import heapq
from typing import List
class Solution:
def eatenApples(self, apples: List[int], days: List[int]) -> int:
ans = i = 0
h = []
while i < len(apples) or h:
# 移除已经腐烂的苹果
while h and h[0][0] <= i:
heapq.heappop(h)
# 如果当天有新苹果生成,加入堆中
if i < len(apples) and apples[i] > 0:
heapq.heappush(h, [i + days[i], apples[i]])
# 吃掉最早腐烂的苹果
if h:
ans += 1
h[0][1] -= 1 # 吃掉一个苹果
if h[0][1] == 0:
heapq.heappop(h)
i += 1 # 进入下一天
return ans
# 示例测试
if __name__ == "__main__":
solution = Solution()
# 示例 1
apples = [1,2,3,5,2]
days = [3,2,1,4,2]
print(solution.eatenApples(apples, days)) # 输出: 7
# 示例 2
apples = [3,0,0,0,0,2]
days = [3,0,0,0,0,2]
print(solution.eatenApples(apples, days)) # 输出: 5
# 自定义示例
apples = [2,1,1,0,0,0]
days = [5,3,2,0,0,0]
print(solution.eatenApples(apples, days)) # 输出: 5
输出:
7
5
5
通过这些示例,你可以观察到代码在不同输入下的表现,进一步理解代码的工作原理。
结束语
希望这篇博客能够帮助你深入理解 1705. 吃苹果的最大数目 这道题的解题思路和实现方法。通过合理利用数据结构(最小堆)和优化循环条件,我们能够高效地解决这个问题,最大化吃掉的苹果数量。如果你在学习过程中有任何疑问或建议,欢迎在评论区交流讨论!
版权声明
本文为个人学习笔记,转载请注明出处。
标签
- 算法
- 堆
- 优先队列
- 贪心算法
- Python
- 数据结构
相关企业
- LeetCode
- Microsoft
提示
在解题过程中,合理选择和使用数据结构(如最小堆)是关键。此外,理解题目要求和示例有助于明确解题思路。
相关链接
- LeetCode 1705. 吃苹果的最大数目
- Python
heapq
使用指南 - 优先队列详解
交流与反馈
如果你对这篇博客有任何建议或疑问,欢迎在下方留言交流。你的反馈是我不断改进的动力!
感谢阅读!祝你学习愉快,算法之路越走越宽!