Maximum Number of Eaten Apples
MedPrompt
There is a special kind of apple tree that grows apples every day for n days. On the ith day, the tree grows apples[i] apples that will rot after days[i] days, that is on day i + days[i] the apples will be rotten and cannot be eaten. On some days, the apple tree does not grow any apples, which are denoted by apples[i] == 0 and days[i] == 0.
You decided to eat at most one apple a day (to keep the doctors away). Note that you can keep eating after the first n days.
Given two integer arrays days and apples of length n, return the maximum number of apples you can eat.
Example 1:
Input: apples = [1,2,3,5,2], days = [3,2,1,4,2]
Output: 7
Explanation: You can eat 7 apples:
- On the first day, you eat an apple that grew on the first day.
- On the second day, you eat an apple that grew on the second day.
- On the third day, you eat an apple that grew on the second day. After this day, the apples that grew on the third day rot.
- On the fourth to the seventh days, you eat apples that grew on the fourth day.Example 2:
Input: apples = [3,0,0,0,0,2], days = [3,0,0,0,0,2]
Output: 5
Explanation: You can eat 5 apples:
- On the first to the third day you eat apples that grew on the first day.
- Do nothing on the fouth and fifth days.
- On the sixth and seventh days you eat apples that grew on the sixth day.
Constraints:
n == apples.length == days.length1 <= n <= 2 * 1040 <= apples[i], days[i] <= 2 * 104days[i] = 0if and only ifapples[i] = 0.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach simulates the process day by day in a straightforward manner. It maintains a simple list of all available apple batches, where a batch consists of apples that grew on the same day and share the same expiry date. For each day, it first adds any new apples that have grown. Then, to decide which apple to eat, it performs a linear scan through the entire list of available batches to find a non-rotten apple that will expire the soonest. After eating one, it moves to the next day. This process continues until no more apples can be grown or eaten.
Algorithm
- Initialize
eatenCount = 0,currentDay = 0, and an empty listavailableApplesto store pairs of[expiryDate, count]. - Start a loop to simulate the passage of time, day by day.
- Inside the loop for
currentDay:- If
currentDayis less thannandapples[currentDay]is positive, add a new batch[currentDay + days[currentDay], apples[currentDay]]to theavailableAppleslist. - Linearly scan the
availableAppleslist to find the batch that is not rotten (expiryDate > currentDay) and has the earliestexpiryDate. - If such an apple batch is found, increment
eatenCountand decrement the apple count of that batch. - Increment
currentDay.
- If
- The loop terminates when
currentDayis pastnand there are no more edible apples left in theavailableAppleslist. - Return the total
eatenCount.
Walkthrough
We use a simple list, for instance, an ArrayList in Java, to store pairs of [expiryDate, count] for each batch of apples. We then loop through time, day by day, starting from day 0.
In each iteration for day d:
- Add New Apples: If
dis within thendays of growth (d < n) andapples[d]is positive, we calculate the expiry dated + days[d]and add a new entry[expiryDate, apples[d]]to our list. - Find Best Apple: We search for the best apple to eat. We initialize a variable
bestAppleIndexto -1 andearliestExpiryto infinity. We then iterate through our list of apple batches. For each batch, we check if it's not rotten (i.e.,expiryDate > d) and if itsexpiryDateis earlier than the currentearliestExpiry. If both conditions are met, we updateearliestExpiryandbestAppleIndex. - Eat Apple: If we found a valid apple to eat (
bestAppleIndex != -1), we increment our total count of eaten apples and decrement the count of the chosen apple batch. - Advance Day: We increment the day
dto move to the next day.
The simulation stops when we are past the n days of growth and a check reveals there are no more edible apples left in our list.
import java.util.ArrayList;import java.util.List; class Solution { public int eatenApples(int[] apples, int[] days) { List<int[]> availableApples = new ArrayList<>(); int eatenCount = 0; int n = apples.length; int currentDay = 0; // Loop until no more apples can be produced or eaten while (true) { // Add new apples that grow on the current day if (currentDay < n && apples[currentDay] > 0) { int expiryDay = currentDay + days[currentDay]; int appleCount = apples[currentDay]; availableApples.add(new int[]{expiryDay, appleCount}); } // Find the non-rotten apple that will expire soonest int bestAppleIndex = -1; int earliestExpiry = Integer.MAX_VALUE; for (int i = 0; i < availableApples.size(); i++) { int[] batch = availableApples.get(i); // Check if the batch is not rotten and has apples if (batch[0] > currentDay && batch[1] > 0) { if (batch[0] < earliestExpiry) { earliestExpiry = batch[0]; bestAppleIndex = i; } } } // If a suitable apple is found, eat it if (bestAppleIndex != -1) { eatenCount++; availableApples.get(bestAppleIndex)[1]--; } currentDay++; // Check for termination condition boolean hasEdibleApples = false; for (int[] batch : availableApples) { if (batch[0] > currentDay && batch[1] > 0) { hasEdibleApples = true; break; } } if (currentDay >= n && !hasEdibleApples) { break; } } return eatenCount; }}Complexity
Time
O(D * n), where `D` is the last day an apple can be eaten (roughly `n + max(days)`) and `n` is the length of the input arrays. The outer loop runs up to `D` times, and inside it, we scan a list of size up to `n`. This quadratic-like complexity is too slow for the given constraints.
Space
O(n), where n is the length of the input arrays. In the worst case, we might store a batch of apples for each of the n days.
Trade-offs
Pros
Conceptually simple and easy to follow.
Directly translates the problem statement into code without complex data structures.
Cons
Extremely inefficient due to the nested loop structure (simulating days and scanning the list of apples).
The time complexity makes it too slow for the given constraints, leading to a 'Time Limit Exceeded' error on most platforms.
A much more efficient approach is to use a greedy strategy. The optimal strategy is to always eat an apple that will rot the soonest. This maximizes the chances of eating apples that would otherwise be wasted, while preserving apples with a longer shelf life for later days. A min-priority queue is the perfect data structure to implement this. It can store apple batches (represented by their expiry date and count) and allows for efficient retrieval of the batch with the earliest expiry date.
Algorithm
- Initialize a min-priority queue
pqto store[expiryDate, count]pairs, ordered byexpiryDate. - Initialize
eatenCount = 0and the current dayd = 0. - Loop as long as there are days left for apples to grow (
d < n) or there are still apples in thepq. - Inside the loop:
- If
d < nandapples[d] > 0, add the new batch[d + days[d], apples[d]]to thepq. - Remove all rotten apple batches from the top of the
pq. A batch is rotten if itsexpiryDate <= d. - If the
pqis not empty after removing rotten ones, it means there's an edible apple. Eat one from the top batch (which expires soonest). - To do this, increment
eatenCount,poll()the top batch, decrement its count, and if the count is still positive,offer()it back to thepq. - Increment the day
d.
- If
- Return
eatenCountafter the loop terminates.
Walkthrough
This approach uses a min-priority queue (min-heap) to keep track of the available apples. The priority queue will store arrays or objects containing two pieces of information: the expiry date and the number of apples in that batch. The queue is ordered based on the expiry date, so the batch that will expire soonest is always at the top.
The simulation proceeds day by day:
- Data Structure: We use a
PriorityQueue<int[]>in Java, where eachint[]is a pair{expiryDate, count}. We provide a custom comparator to sort byexpiryDate. - Iteration: We loop with a day counter
d, starting from 0. The loop continues as long as we are within thendays of growth or there are still apples in our priority queue. - Add New Apples: On day
d, ifd < nandapples[d] > 0, we add a new batch{d + days[d], apples[d]}to the priority queue. This operation takesO(log k)time, wherekis the current size of the queue. - Remove Rotten Apples: Before eating, we must discard any apples that have already rotted. We peek at the top of the queue and, as long as it's not empty and its expiry date is less than or equal to the current day
d, we poll it from the queue. - Eat an Apple: After cleaning up, if the queue is not empty, we can eat an apple. We poll the top batch (the one expiring soonest), increment our
eatenApplescount, and decrement its apple count. If apples remain in this batch (count > 0), we add it back to the queue.
This process ensures that on any given day, we make the optimal greedy choice, leading to the maximum number of eaten apples.
import java.util.PriorityQueue; class Solution { public int eatenApples(int[] apples, int[] days) { // Min-heap storing pairs of [expiryDay, appleCount] // Ordered by expiryDay PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]); int eatenCount = 0; int n = apples.length; int i = 0; // Loop continues as long as there are days left to grow apples (i < n) // or there are still apples in the priority queue to be eaten. while (i < n || !pq.isEmpty()) { // 1. Add apples that grow today if (i < n && apples[i] > 0) { int expiryDay = i + days[i]; int appleCount = apples[i]; pq.offer(new int[]{expiryDay, appleCount}); } // 2. Remove rotten apples from the top of the queue while (!pq.isEmpty() && pq.peek()[0] <= i) { pq.poll(); } // 3. Eat one apple if available if (!pq.isEmpty()) { eatenCount++; int[] earliestBatch = pq.poll(); earliestBatch[1]--; // If there are still apples left in this batch, add it back if (earliestBatch[1] > 0) { pq.offer(earliestBatch); } } // Move to the next day i++; } return eatenCount; }}Complexity
Time
O(D * log n), where `D` is the last day an apple can be eaten (at most `n + max(days)`) and `n` is the number of days apples grow. The main loop runs `D` times, and each iteration involves a few priority queue operations, which take `O(log n)` time. This is efficient enough for the given constraints.
Space
O(n), where n is the number of days apples grow. In the worst-case scenario, the priority queue might need to store a distinct batch of apples for each of the `n` days.
Trade-offs
Pros
Highly efficient and provides the optimal solution.
The greedy choice is provably correct for this problem.
Passes all test cases within the given time and memory constraints.
Cons
Requires knowledge of priority queues (min-heaps).
Slightly more complex to implement compared to a simple list-based simulation.
Solutions
Solution
class Solution {public int eatenApples(int[] apples, int[] days) { PriorityQueue<int[]> q = new PriorityQueue<>(Comparator.comparingInt(a->a[0])); int n = days.length; int ans = 0, i = 0; while (i < n || !q.isEmpty()) { if (i < n && apples[i] > 0) { q.offer(new int[]{i + days[i] - 1, apples[i]}); } while (!q.isEmpty() && q.peek()[0] < i) { q.poll(); } if (!q.isEmpty()) { var p = q.poll(); ++ans; if (--p[1] > 0 && p[0] > i) { q.offer(p); } } ++i; } return ans; }}using pii = pair < int , int > ; class Solution { public: int eatenApples ( vector < int >& apples , vector < int >& days ) { priority_queue < pii , vector < pii > , greater < pii >> q ; int n = days . size (); int ans = 0 , i = 0 ; while ( i < n || ! q . empty ()) { if ( i < n && apples [ i ]) q . emplace ( i + days [ i ] - 1 , apples [ i ]); while ( ! q . empty () && q . top (). first < i ) q . pop (); if ( ! q . empty ()) { auto [ t , v ] = q . top (); q . pop (); -- v ; ++ ans ; if ( v && t > i ) q . emplace ( t , v ); } ++ i ; } return ans ; } };class Solution: def eatenApples(self, apples: List[int], days: List[int]) -> int: n = len(days) i = ans = 0 q = [] while i < n or q: if i < n and apples[i]: heappush(q, (i + days[i] - 1, apples[i])) while q and q[0][0] < i: heappop(q) if q: t, v = heappop(q) v -= 1 ans += 1 if v and t > i: heappush(q, (t, v)) i += 1 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.