24/12/24 力扣每日一题 # 题目解析:1705. 吃苹果的最大数目 全网最详细解释

题目解析: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天长出的1个苹果。
  2. 第2天,吃第2天长出的2个苹果中的一个。
  3. 第3天,吃第2天长出的另一个苹果(第3天苹果会腐烂)。
  4. 第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个苹果。

解题思路

为了最大化吃掉的苹果数量,我们需要合理安排每天吃哪个苹果,以避免苹果腐烂。由于苹果有保质期(即腐烂时间),需要优先吃那些即将腐烂的苹果。

步骤详解

  1. 数据结构选择:

    • 使用**最小堆(Min Heap)**来管理苹果,堆中存储的是每批苹果的过期时间和数量。这样,堆顶元素始终是最早过期的一批苹果,便于优先处理。
  2. 处理流程:

    • 遍历每一天,处理当天生成的苹果。
    • 移除堆中已经腐烂的苹果。
    • 添加当天新生成的苹果到堆中。
    • 吃掉堆顶的苹果(即最早腐烂的苹果)。
    • 统计总共吃掉的苹果数量。
    • 继续循环,直到所有生成的苹果都被处理完毕,即使超过n天。
  3. 循环条件:

    • 循环继续的条件是当前天数 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

代码详解

  1. 导入必要的模块:

    import heapq
    from typing import List
    
    • heapq 用于实现最小堆。
    • List 是类型提示的一部分,帮助代码更易读。
  2. 初始化变量:

    ans = i = 0
    h = []
    
    • ans 用于统计总共吃掉的苹果数量。
    • i 表示当前天数,从0开始。
    • h 是一个空的最小堆,用于存储苹果的过期时间和数量。
  3. 主循环:

    while i < len(apples) or h:
    
    • 条件解释:
      • i < len(apples):确保在所有生成苹果的天数内,处理每天生成的苹果。
      • or h:确保即使在所有天数之后,仍然有未腐烂的苹果时,继续处理堆中的苹果。
  4. 移除已腐烂的苹果:

    while h and h[0][0] <= i:
        heapq.heappop(h)
    
    • 检查堆顶的苹果是否已经腐烂(即过期时间 <= 当前天数)。
    • 如果是,移出堆顶元素,直到堆顶的苹果都是未腐烂的。
  5. 添加当天新生成的苹果:

    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]] 作为一个列表加入堆中。
  6. 吃掉苹果:

    if h:
        ans += 1
        h[0][1] -= 1  # 吃掉一个苹果
        if h[0][1] == 0:
            heapq.heappop(h)
    
    • 如果堆中还有未腐烂的苹果,取出堆顶元素 [过期时间, 剩余数量]
    • 吃掉一个苹果,ans 增加1,剩余数量 减少1。
    • 如果这一批苹果已经被全部吃完(剩余数量 == 0),将其移出堆中。
  7. 进入下一天:

    i += 1
    
    • 增加 i,进入下一天的循环。
  8. 返回结果:

    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]]
  • 第1天(i=1):

    • 生成1个苹果,过期时间为 1 + 3 = 4
    • 堆中有 [[4, 1], [5, 1]]
    • 吃掉过期时间最早的苹果(过期时间4),堆中剩余 [[5, 1]]
  • 第2天(i=2):

    • 生成1个苹果,过期时间为 2 + 2 = 4
    • 堆中有 [[4, 1], [5, 1]]
    • 吃掉过期时间4的苹果,堆中剩余 [[5, 1]]
  • 第3天(i=3):

    • 没有新苹果生成。
    • 检查堆中苹果是否过期:
      • 4 <= 3 不成立,堆中仍有 [[5, 1]]
    • 吃掉堆顶的苹果,堆中剩余 [[5, 0]],移出堆中。
  • 第4天(i=4)和第5天(i=5):

    • 没有新苹果生成。
    • 堆中没有未腐烂的苹果,循环结束。

如果不使用 or h

  • i 达到 6 时,循环会停止,无法继续处理堆中的苹果,导致部分苹果未被吃掉。

时间与空间复杂度分析

  • 时间复杂度: O(n log n)

    • 每天可能需要进行一次 heappushheappop 操作,堆的操作时间复杂度为 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: 不满足,因此不会将新苹果加入堆中。但如果堆中还有未腐烂的苹果,仍然会继续吃苹果,直到所有苹果都被吃掉或腐烂。

总结

通过合理利用最小堆的特性,我们能够高效地管理苹果的过期时间,确保每次都优先吃掉即将腐烂的苹果,从而最大化吃掉的苹果数量。这道题考察了对优先队列(堆)的应用,以及如何在有限条件下优化资源的使用。掌握了这种思路和技巧,不仅能解决类似的问题,还能提升对数据结构的理解和应用能力。

进一步阅读

代码运行示例

以下是一个完整的代码运行示例,帮助你更好地理解代码的执行过程:

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
  • Google
  • Microsoft
  • Facebook

提示

在解题过程中,合理选择和使用数据结构(如最小堆)是关键。此外,理解题目要求和示例有助于明确解题思路。

相关链接

交流与反馈

如果你对这篇博客有任何建议或疑问,欢迎在下方留言交流。你的反馈是我不断改进的动力!


感谢阅读!祝你学习愉快,算法之路越走越宽!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值