贪心题目:吃苹果的最大数目

文章目录

题目

标题和出处

标题:吃苹果的最大数目

出处:1705. 吃苹果的最大数目

难度

6 级

题目描述

要求

有一棵特殊的苹果树,连续 n \texttt{n} n 天,每天都可以长出若干个苹果。在第 i \texttt{i} i 天,树上会长出 applesi \texttt{applesi} applesi 个苹果,这些苹果将会在 daysi \texttt{daysi} daysi 天后(第 i + daysi \texttt{i} + \texttt{daysi} i+daysi 天时)腐烂而无法食用。也可能有那么几天,树上不会长出新的苹果,此时用 applesi = 0 \texttt{applesi} = \texttt{0} applesi=0 且 daysi = 0 \texttt{daysi} = \texttt{0} daysi=0 表示。

每天最多 吃一个苹果。可以在 n \texttt{n} n 天之后继续吃苹果。

给定两个长度为 n \texttt{n} n 的整数数组 days \texttt{days} days 和 apples \texttt{apples} apples,返回可以吃掉的苹果的最大数目。

示例

示例 1:

输入: apples = 1,2,3,5,2, days = 3,2,1,4,2 \texttt{apples = 1,2,3,5,2, days = 3,2,1,4,2} apples = 1,2,3,5,2, days = 3,2,1,4,2

输出: 7 \texttt{7} 7

解释:可以吃掉 7 \texttt{7} 7 个苹果:

  • 第一天,吃掉第一天长出来的苹果。
  • 第二天,吃掉一个第二天长出来的苹果。
  • 第三天,吃掉一个第二天长出来的苹果。过了这一天,第三天长出来的苹果就已经腐烂了。
  • 第四天到第七天,吃的都是第四天长出来的苹果。

示例 2:

输入: apples = 3,0,0,0,0,2, days = 3,0,0,0,0,2 \texttt{apples = 3,0,0,0,0,2, days = 3,0,0,0,0,2} apples = 3,0,0,0,0,2, days = 3,0,0,0,0,2

输出: 5 \texttt{5} 5

解释:可以吃掉 5 \texttt{5} 5 个苹果:

  • 第一天到第三天,吃的都是第一天长出来的苹果。
  • 第四天和第五天不吃苹果。
  • 第六天和第七天,吃的都是第六天长出来的苹果。

数据范围

  • n = apples.length = days.length \texttt{n} = \texttt{apples.length} = \texttt{days.length} n=apples.length=days.length
  • 1 ≤ n ≤ 2 × 10 4 \texttt{1} \le \texttt{n} \le \texttt{2} \times \texttt{10}^\texttt{4} 1≤n≤2×104
  • 0 ≤ applesi, daysi ≤ 2 × 10 4 \texttt{0} \le \texttt{applesi, daysi} \le \texttt{2} \times \texttt{10}^\texttt{4} 0≤applesi, daysi≤2×104
  • 当且仅当 applesi = 0 \texttt{applesi} = \texttt{0} applesi=0 时, daysi = 0 \texttt{daysi} = \texttt{0} daysi=0 才成立

解法

思路和算法

为方便表述,将未腐烂的苹果称为新鲜苹果,将苹果开始腐烂的前一天称为苹果的最后有效期。

为了将吃苹果的数目最大化,应使用贪心策略,遵循以下两点原则。

  1. 如果当天有剩余的新鲜苹果,则应该吃苹果。理由如下:如果当天不吃苹果,则剩余的新鲜苹果的有效期减少一天,可以吃的苹果数目不变或减少,不可能增加。

  2. 如果当天有多个剩余的新鲜苹果,则应该吃最后有效期最近的苹果。理由如下:用 a a a 和 b b b 分别表示两个苹果,其中 a a a 苹果是最后有效期最近的苹果, b b b 苹果的最后有效期大于 a a a 苹果,如果先吃 a a a 苹果则一定能在第二天吃 b b b 苹果,如果先吃 b b b 苹果则可能在第二天 a a a 苹果腐烂而无法使用,因此先吃 b b b 苹果的情况下可以吃的苹果数目小于等于先吃 a a a 苹果的情况下可以吃的苹果数目,先吃 a a a 苹果的情况下可以吃的苹果数目最多。

根据贪心策略,可以模拟吃苹果的过程,当有剩余的新鲜苹果时,吃最后有效期最近的苹果。为了得到最后有效期最近的苹果,应使用优先队列存储苹果的最后有效期,优先队列的队首元素是最近的最后有效期。

由于每天长出苹果的数目最多是 2 × 10 4 2 \times 10^4 2×104 个,长出苹果的天数最多是 2 × 10 4 2 \times 10^4 2×104 天,因此苹果总数最多是 4 × 10 8 4 \times 10^8 4×108 个,不能直接使用优先队列存储每个苹果的最后有效期。为了降低复杂度,优先队列应存储二元组,每个二元组包含苹果的最后有效期和苹果数目,优先队列的队首二元组包含最近的最后有效期和对应的苹果数目,此时优先队列中的二元组数目最多为长出苹果的天数 n n n。

吃苹果的过程可以分成两个阶段:第一阶段是第 0 0 0 天到第 n − 1 n - 1 n−1 天,每天都可能长出苹果,需要同时考虑新长出的苹果和吃掉的苹果;第二阶段是第 n n n 天及以后,不会再长出苹果,只需要考虑吃掉的苹果。

第一阶段中,对于每个下标 i i i,执行如下操作。

  1. 将优先队列中的所有最后有效期小于 i i i 的二元组取出,这些二元组表示第 i i i 天的时候已经腐烂的苹果,无法食用。

  2. 如果 apples i > 0 \textit{apples}i > 0 applesi>0,则第 i i i 天长出苹果,新长出的苹果的最后有效期是 i + days i − 1 i + \textit{days}i - 1 i+daysi−1,将二元组 ( i + days i − 1 , apples i ) (i + \textit{days}i - 1, \textit{apples}i) (i+daysi−1,applesi) 加入优先队列。

  3. 如果优先队列不为空,则第 i i i 天可以吃一个新鲜苹果,对应优先队列的队首二元组。将吃苹果的数目加 1 1 1,用 top \textit{top} top 表示优先队列的队首二元组,则该二元组中的最后有效期是 top 0 \textit{top}0 top0,苹果数目是 top 1 \textit{top}1 top1,将 top 1 \textit{top}1 top1 减 1 1 1,如果 top 1 \textit{top}1 top1 变成 0 0 0 则将 top \textit{top} top 从优先队列中取出。

第二阶段中,使用 day \textit{day} day 表示当前日期,初始时 day = n \textit{day} = n day=n,执行如下操作。

  1. 将优先队列中的所有最后有效期小于 day \textit{day} day 的二元组取出,这些二元组表示第 day \textit{day} day 天的时候已经腐烂的苹果,无法食用。

  2. 如果优先队列不为空,则从第 day \textit{day} day 天开始可以吃一个或多个新鲜苹果,对应优先队列的队首二元组。用 top \textit{top} top 表示优先队列的队首二元组,则该二元组中的最后有效期是 top 0 \textit{top}0 top0,苹果数目是 top 1 \textit{top}1 top1,从第 day \textit{day} day 天到最后有效期共有 top 0 − day + 1 \textit{top}0 - \textit{day} + 1 top0−day+1 天,因此可以吃的苹果数目是 count = min ⁡ ( top 0 − day + 1 , top 1 ) \textit{count} = \min(\textit{top}0 - \textit{day} + 1, \textit{top}1) count=min(top0−day+1,top1),将吃苹果的数目加 count \textit{count} count,将 day \textit{day} day 加 count \textit{count} count。

当优先队列为空时,操作结束,此时得到吃苹果的最大数目。

代码

java 复制代码
class Solution {
    public int eatenApples(int[] apples, int[] days) {
        int total = 0;
        PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[0] - b[0]);
        int n = apples.length;
        for (int i = 0; i < n; i++) {
            while (!pq.isEmpty() && pq.peek()[0] < i) {
                pq.poll();
            }
            if (apples[i] > 0) {
                pq.offer(new int[]{i + days[i] - 1, apples[i]});
            }
            if (!pq.isEmpty()) {
                total++;
                int[] top = pq.peek();
                top[1]--;
                if (top[1] == 0) {
                    pq.poll();
                }
            }
        }
        int day = n;
        while (!pq.isEmpty()) {
            while (!pq.isEmpty() && pq.peek()[0] < day) {
                pq.poll();
            }
            if (!pq.isEmpty()) {
                int[] top = pq.poll();
                int count = Math.min(top[0] - day + 1, top[1]);
                total += count;
                day += count;
            }
        }
        return total;
    }
}

复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n 是数组 apples \textit{apples} apples 和 days \textit{days} days 的长度。需要遍历数组 apples \textit{apples} apples 和 days \textit{days} days 一次,优先队列中最多有 n n n 个二元组,每个二元组最多加入优先队列和从优先队列中取出各一次,因此时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 apples \textit{apples} apples 和 days \textit{days} days 的长度。优先队列最多有 n n n 个二元组。

相关推荐
吞下星星的少年·-·9 天前
Div.2 CF 1130B题(构造,贪心)
算法·贪心·构造
伟大的车尔尼12 天前
贪心题目:加油站
贪心
伟大的车尔尼13 天前
贪心题目:给定行和列的和求可行矩阵
贪心
伟大的车尔尼14 天前
贪心题目:需要教语言的最少人数
贪心
lvwangshu15 天前
T3 涂鸦带师 题解
题解·贪心·倍增·技巧·思维好题·断环成链
吞下星星的少年·-·17 天前
牛客周赛 119B题(贪心)
算法·贪心
吞下星星的少年·-·18 天前
Div.2 CF edu195A题(数学,贪心)
数学·算法·贪心
伟大的车尔尼19 天前
贪心题目:交换一次的先前排列
贪心
lvwangshu20 天前
CF1882D Tree XOR 题解
动态规划·题解·贪心·dp·树形dp·换根dp·性质题·思维好题·推导题