力扣题解 第4期:1705. 吃苹果的最大数目

437 阅读3分钟

1、题干

有一棵特殊的苹果树,一连 n 天,每天都可以长出若干个苹果。在第 i 天,树上会长出 apples[i] 个苹果,这些苹果将会在 days[i] 天后(也就是说,第 i + days[i] 天时)腐烂,变得无法食用。也可能有那么几天,树上不会长出新的苹果,此时用 apples[i] == 0 且 days[i] == 0 表示。

你打算每天 最多 吃一个苹果来保证营养均衡。注意,你可以在这 n 天之后继续吃苹果。

给你两个长度为 n 的整数数组 days 和 apples ,返回你可以吃掉的苹果的最大数目

 

示例 1:

输入: apples = [1,2,3,5,2], days = [3,2,1,4,2]
输出: 7
解释: 你可以吃掉 7 个苹果:

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

示例 2:

输入: apples = [3,0,0,0,0,2], days = [3,0,0,0,0,2]
输出: 5
解释: 你可以吃掉 5 个苹果:

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

 

提示:

  • apples.length == n
  • days.length == n
  • 1 <= n <= 2 * 104
  • 0 <= apples[i], days[i] <= 2 * 104
  • 只有在 apples[i] = 0 时,days[i] = 0 才成立

2、解题思路

  • 总体思路:使用贪心策略,在尚未腐烂的苹果中优先选择保质期最小的苹果。
  • 关键思路:基于计数排序求保质期最小的苹果,用数组存储苹果数量和保质期,数组的下标表示保质期、值表示该日期对应的苹果数量;找保质期最小的苹果只需要以上一个最小保质期为起点,遍历数组找到下标最小且值不为0的元素。
  • 时间复杂度:由于遍历的起点是上一个最小的保质期,因此总体实现出来的时间复杂度是O(n)O(n),其中nn是最大保质期。

下面说明代码实现细节:

  • 计数排序容器记为freshArr,其下标表示保质期、值表示该保质期之前的苹果数量
  • 最小保质期记为minDay,用于记录有苹果剩余的最小保质期
  • 可以吃掉苹果的最大数量记为res,即最终结果
  • 接下来以最大保质期为限制逐天遍历,天数记为i
  • 在第i天时,如果i没有超过n且过期天数大于0(i < days.length && days[i] > 0),进行以下操作
    • 在计数排序容器freshArr中按保质期i + days[i] - 1累加苹果数量
    • 更新最小保质期minDay = Math.min(minDay, i + days[i] - 1)
  • 接下来只要找到有苹果剩余的最小保质期即可
    • 先确保最小保质期在今天之后,即minDay = Math.max(minDay, i)
    • 然后在计数排序容器freshArr中遍历,找到有苹果剩余的最小保质期
  • 接下来更新最终结果和计数排序容器freshArr中的苹果数量
    • 注意更新之前确定该保质期有苹果剩余,即if (freshArr[minDay]) res++, freshArr[minDay]--
  • 最后就得到可以吃掉苹果的最大数量res

3、代码

var eatenApples = function (apples, days) {
    let res = 0, minDay = days[0] - 1;
    const freshArr = new Array(days.length).fill(0);
    for (let i = 0; i < freshArr.length; i++) {
        if (i < days.length && days[i] > 0) {
            freshArr[i + days[i] - 1] = (freshArr[i + days[i] - 1] || 0) + apples[i];
            minDay = Math.min(minDay, i + days[i] - 1);
        }
        minDay = Math.max(minDay, i);
        while (minDay < freshArr.length && !freshArr[minDay]) minDay++;
        if (freshArr[minDay]) res++, freshArr[minDay]--;
    }
    return res;
};

4、执行结果

图片

后记

本题使用最大堆、最小堆、优先队列等方式解题会更合适,但这里使用的是计数排序,主要有几点考虑:

  • JS没有最大堆、最小堆、优先队列这种东西,每次遇到都copy一个或者手写一个实在费劲
  • 虽然最大堆、最小堆的时间复杂度很优秀,但其实现比较繁琐,晓编相信有更好的方法,所以积极尝试打破桎梏
  • 解题方面追求有很多,消耗时间更少、消耗空间更少、代码更精简、思路更简单等等,大多时候不能兼得;这里取了后面两点,代码和思路都简单很多,另外两点也不算太糟糕