1705. Maximum Number of Eaten Apples
Problem Description
You have a special apple tree that produces apples for n
consecutive days. On each day i
(0-indexed), the tree grows apples[i]
fresh apples. These apples have a shelf life - they will rot after days[i]
days, meaning they become inedible on day i + days[i]
.
Some days the tree doesn't produce any apples, indicated by apples[i] == 0
and days[i] == 0
.
You want to eat apples following these rules:
- You can eat at most one apple per day
- You can only eat fresh (non-rotten) apples
- You can continue eating apples even after day
n
(after the tree stops producing)
Given two arrays apples
and days
of length n
, where:
apples[i]
represents the number of apples grown on dayi
days[i]
represents how many days those apples will stay fresh
Your goal is to find the maximum number of apples you can eat.
For example, if on day 0 the tree grows 3 apples that last for 2 days, these apples can be eaten on days 0 and 1 (they rot on day 2). You would eat one apple on day 0 and one on day 1, leaving one apple uneaten as it would rot before day 2.
The strategy involves using a min-heap to track apples by their expiration dates. Each day, you:
- Add any new apples produced that day to the heap (with their expiration date)
- Remove any rotten apples from the heap
- Eat one apple from those expiring soonest (if available)
- Continue this process until no fresh apples remain
Intuition
When faced with this problem, we need to think about which apple to eat each day to maximize the total number of apples eaten. The key insight is that apples have expiration dates, and once they rot, they're gone forever.
Consider this scenario: on day 0, you have apples that expire on day 3 and apples that expire on day 10. If you eat the apples expiring on day 10 first, you might miss the opportunity to eat the apples expiring on day 3 - they'll rot before you get to them. However, if you eat the apples expiring on day 3 first, you can still eat the ones expiring on day 10 later.
This leads us to a greedy strategy: always eat the apple that will expire soonest. This ensures we don't waste apples by letting them rot when we could have eaten them.
To implement this strategy efficiently, we need a data structure that can:
- Quickly give us the apple with the earliest expiration date
- Allow us to add new apples as they're produced
- Remove expired apples
A min-heap (priority queue) is perfect for this. We store apples with their expiration date as the key, so the heap always gives us the apple closest to rotting.
The process continues even after day n
because we might still have fresh apples in storage. Each day follows the same pattern:
- Add any newly grown apples to our heap (if we're still within the first
n
days) - Remove any apples that have rotted
- Eat one apple from those available (choosing the one expiring soonest)
- Move to the next day
This greedy approach ensures we never let an apple rot if we could have eaten it instead, maximizing our total consumption.
Learn more about Greedy and Heap (Priority Queue) patterns.
Solution Approach
Let's walk through the implementation using a greedy approach with a min-heap priority queue.
Data Structure Setup:
We use a min-heap q
to store tuples of (expiration_day, apple_count)
. Python's heapq
module provides a min-heap where elements are ordered by the first value in the tuple.
Algorithm Steps:
-
Initialize variables:
i
tracks the current day (starting from day 0)ans
counts the total apples eatenq
is our min-heap priority queue
-
Main loop continues while either:
- We're still within the first
n
days (i < n
), OR - We have apples in our heap (
q
is not empty)
- We're still within the first
-
For each day
i
:Step 1: Add new apples (if any)
if i < n and apples[i]: heappush(q, (i + days[i] - 1, apples[i]))
- Only add if we're within the first
n
days and apples were produced - The expiration day is
i + days[i] - 1
(apples grown on dayi
rot on dayi + days[i]
) - Store the count of apples with their expiration date
Step 2: Remove rotten apples
while q and q[0][0] < i: heappop(q)
- Check the apple batch with the earliest expiration date
- If it expired before today (expiration day < current day), remove it
- Continue until all expired apples are removed
Step 3: Eat one apple (if available)
if q: t, v = heappop(q) # Get batch with earliest expiration v -= 1 # Eat one apple ans += 1 # Increment count if v and t > i: # If apples remain and aren't expiring today heappush(q, (t, v)) # Put back remaining apples
- Take the batch that expires soonest
- Eat one apple from it
- If there are remaining apples in that batch and they won't expire today, put them back in the heap
- Only add if we're within the first
-
Move to next day:
i += 1
Time Complexity: O(n log n)
where n
is the number of days. Each day we might push and pop from the heap, which takes O(log n)
time.
Space Complexity: O(n)
for storing apples in the heap in the worst case.
The key insight is that by always eating the apple that will expire soonest, we ensure no apple goes to waste if it could have been eaten, thus maximizing our total consumption.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Input:
apples = [2, 1, 10]
days = [2, 10, 1]
This means:
- Day 0: 2 apples grow, lasting 2 days (expire on day 2)
- Day 1: 1 apple grows, lasting 10 days (expire on day 11)
- Day 2: 10 apples grow, lasting 1 day (expire on day 3)
Step-by-step execution:
Day 0:
- Add 2 apples expiring on day 1 (0 + 2 - 1) to heap:
heap = [(1, 2)]
- No rotten apples to remove
- Eat 1 apple from batch expiring on day 1
- 1 apple remains, put back:
heap = [(1, 1)]
- Total eaten: 1
Day 1:
- Add 1 apple expiring on day 10 (1 + 10 - 1) to heap:
heap = [(1, 1), (10, 1)]
- No rotten apples (day 1 apples expire at end of day 1)
- Eat 1 apple from batch expiring on day 1 (earliest expiration)
- That batch is now empty
heap = [(10, 1)]
- Total eaten: 2
Day 2:
- Add 10 apples expiring on day 2 (2 + 1 - 1) to heap:
heap = [(2, 10), (10, 1)]
- No rotten apples yet
- Eat 1 apple from batch expiring on day 2 (earliest expiration)
- 9 apples remain, put back:
heap = [(2, 9), (10, 1)]
- Total eaten: 3
Day 3:
- No new apples (past day n-1)
- Remove batch expiring on day 2 (2 < 3, so they're rotten)
heap = [(10, 1)]
- Eat 1 apple from batch expiring on day 10
- That batch is now empty
heap = []
- Total eaten: 4
Day 4 and beyond:
- Heap is empty, no more apples to eat
- Algorithm terminates
Final answer: 4 apples eaten
Notice how the greedy strategy worked: we ate the apples expiring on days 1 and 2 first (even though we had apples lasting until day 10), ensuring we didn't waste them. This maximized our total consumption.
Solution Implementation
1class Solution:
2 def eatenApples(self, apples: List[int], days: List[int]) -> int:
3 """
4 Calculates maximum number of apples that can be eaten.
5 Each day i produces apples[i] apples that will rot after days[i] days.
6 You can eat at most one apple per day.
7
8 Args:
9 apples: List where apples[i] is number of apples produced on day i
10 days: List where days[i] is number of days before apples from day i rot
11
12 Returns:
13 Maximum number of apples that can be eaten
14 """
15 n = len(days)
16 current_day = 0
17 total_eaten = 0
18
19 # Min heap storing (expiration_day, apple_count) tuples
20 # Priority queue ordered by expiration date
21 heap = []
22
23 # Continue while there are days to process or apples to eat
24 while current_day < n or heap:
25 # Add new apples produced on current day to the heap
26 if current_day < n and apples[current_day] > 0:
27 expiration_day = current_day + days[current_day] - 1
28 heappush(heap, (expiration_day, apples[current_day]))
29
30 # Remove all expired apples from the heap
31 while heap and heap[0][0] < current_day:
32 heappop(heap)
33
34 # Eat one apple if available
35 if heap:
36 expiration_day, apple_count = heappop(heap)
37 apple_count -= 1
38 total_eaten += 1
39
40 # Put remaining apples back if they haven't expired
41 if apple_count > 0 and expiration_day > current_day:
42 heappush(heap, (expiration_day, apple_count))
43
44 current_day += 1
45
46 return total_eaten
47
1class Solution {
2 public int eatenApples(int[] apples, int[] days) {
3 // Min heap to store apple batches, sorted by expiration day
4 // Each element is [expirationDay, remainingApples]
5 PriorityQueue<int[]> minHeap = new PriorityQueue<>(
6 Comparator.comparingInt(batch -> batch[0])
7 );
8
9 int totalDays = days.length;
10 int applesEaten = 0;
11 int currentDay = 0;
12
13 // Continue until we've processed all days and heap is empty
14 while (currentDay < totalDays || !minHeap.isEmpty()) {
15 // Add new apples that grow on current day (if within harvest period)
16 if (currentDay < totalDays && apples[currentDay] > 0) {
17 // Calculate expiration day: current day + days until rot - 1
18 int expirationDay = currentDay + days[currentDay] - 1;
19 minHeap.offer(new int[] {expirationDay, apples[currentDay]});
20 }
21
22 // Remove all expired apple batches
23 while (!minHeap.isEmpty() && minHeap.peek()[0] < currentDay) {
24 minHeap.poll();
25 }
26
27 // Eat one apple from the batch that expires soonest (if available)
28 if (!minHeap.isEmpty()) {
29 int[] earliestBatch = minHeap.poll();
30 applesEaten++;
31
32 // Decrement apple count and re-add batch if still has apples and not expired
33 earliestBatch[1]--;
34 if (earliestBatch[1] > 0 && earliestBatch[0] > currentDay) {
35 minHeap.offer(earliestBatch);
36 }
37 }
38
39 // Move to next day
40 currentDay++;
41 }
42
43 return applesEaten;
44 }
45}
46
1class Solution {
2public:
3 int eatenApples(vector<int>& apples, vector<int>& days) {
4 // Define pair type: {expiration_day, apple_count}
5 using PairType = pair<int, int>;
6
7 // Min-heap ordered by expiration day (earliest expiration first)
8 priority_queue<PairType, vector<PairType>, greater<PairType>> minHeap;
9
10 int totalDays = days.size();
11 int totalEaten = 0;
12 int currentDay = 0;
13
14 // Continue while we have new apples to receive or stored apples to eat
15 while (currentDay < totalDays || !minHeap.empty()) {
16 // Add new apples received on current day if within harvest period
17 if (currentDay < totalDays && apples[currentDay] > 0) {
18 int expirationDay = currentDay + days[currentDay] - 1;
19 minHeap.emplace(expirationDay, apples[currentDay]);
20 }
21
22 // Remove all expired apples (expiration day before current day)
23 while (!minHeap.empty() && minHeap.top().first < currentDay) {
24 minHeap.pop();
25 }
26
27 // Eat one apple from the batch that expires earliest (if available)
28 if (!minHeap.empty()) {
29 auto [expirationDay, appleCount] = minHeap.top();
30 minHeap.pop();
31
32 // Eat one apple
33 appleCount--;
34 totalEaten++;
35
36 // If there are remaining apples that haven't expired, put them back
37 if (appleCount > 0 && expirationDay > currentDay) {
38 minHeap.emplace(expirationDay, appleCount);
39 }
40 }
41
42 // Move to next day
43 currentDay++;
44 }
45
46 return totalEaten;
47 }
48};
49
1function eatenApples(apples: number[], days: number[]): number {
2 // Priority queue to store apples with their expiration dates
3 // Each element is [expirationDay, appleCount]
4 // Using array to simulate min-heap (will sort after each operation)
5 const minHeap: [number, number][] = [];
6
7 // Helper function to maintain heap property (sorts by expiration day)
8 const maintainHeap = (): void => {
9 minHeap.sort((a, b) => a[0] - b[0]);
10 };
11
12 const totalDays = days.length;
13 let totalEaten = 0;
14 let currentDay = 0;
15
16 // Continue while we have new apples to receive or stored apples to eat
17 while (currentDay < totalDays || minHeap.length > 0) {
18 // Add new apples received on current day if within harvest period
19 if (currentDay < totalDays && apples[currentDay] > 0) {
20 // Calculate when these apples will expire (last day they can be eaten)
21 const expirationDay = currentDay + days[currentDay] - 1;
22 minHeap.push([expirationDay, apples[currentDay]]);
23 maintainHeap();
24 }
25
26 // Remove all expired apples (expiration day before current day)
27 while (minHeap.length > 0 && minHeap[0][0] < currentDay) {
28 minHeap.shift();
29 }
30
31 // Eat one apple from the batch that expires earliest (if available)
32 if (minHeap.length > 0) {
33 // Get the batch with earliest expiration
34 const [expirationDay, appleCount] = minHeap.shift()!;
35
36 // Eat one apple from this batch
37 const remainingApples = appleCount - 1;
38 totalEaten++;
39
40 // If there are remaining apples that haven't expired, put them back
41 if (remainingApples > 0 && expirationDay > currentDay) {
42 minHeap.push([expirationDay, remainingApples]);
43 maintainHeap();
44 }
45 }
46
47 // Move to next day
48 currentDay++;
49 }
50
51 return totalEaten;
52}
53
Time and Space Complexity
Time Complexity: O(n × log n + M)
where n
is the length of the arrays apples
and days
, and M
represents the maximum value in the days
array.
The analysis breaks down as follows:
- The while loop continues for at most
n + M
iterations (we processn
days of receiving apples, then potentially continue eating for up toM
more days after the last apple is received) - Within each iteration:
heappush
operation when adding new apples:O(log n)
- happens at mostn
times total- The inner while loop for removing expired apples: each apple batch is removed at most once, so this is amortized
O(1)
per iteration heappop
and potentialheappush
for eating an apple:O(log n)
per iteration
- Total operations:
n
iterations with heap operations give usO(n × log n)
, plus potentiallyM
additional iterations after dayn
, each withO(log n)
heap operations - Since the heap size is bounded by
n
(at mostn
batches of apples), the overall complexity isO(n × log n + M × log n)
which simplifies toO(n × log n + M)
whenM
could be much larger thann × log n
Space Complexity: O(n)
The heap q
stores at most n
elements, as we can have at most one batch of apples from each day. Each element in the heap is a tuple containing the expiration day and the count of apples, requiring O(1)
space per element. Therefore, the total space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Expiration Date Calculation
Pitfall: A common mistake is calculating the expiration date as i + days[i]
instead of i + days[i] - 1
. This off-by-one error occurs because developers often misinterpret when apples actually rot.
Why it happens: The problem states apples rot after days[i]
days, meaning they become inedible on day i + days[i]
. However, the last day they can be eaten is day i + days[i] - 1
.
Example:
- Apples grown on day 0 with
days[0] = 2
can be eaten on days 0 and 1 - They rot on day 2 (day 0 + 2)
- So the expiration date should be day 1 (day 0 + 2 - 1)
Solution:
# Correct expiration_day = current_day + days[current_day] - 1 # Incorrect (would allow eating rotten apples) expiration_day = current_day + days[current_day]
2. Not Handling Zero Production Days Properly
Pitfall: Forgetting to check if apples[i] > 0
before adding to the heap, which could add unnecessary entries with zero apples.
Why it happens: The problem mentions some days have no production (apples[i] == 0
and days[i] == 0
), but it's easy to overlook this check.
Solution:
# Correct - only add if apples were actually produced if current_day < n and apples[current_day] > 0: heappush(heap, (expiration_day, apples[current_day])) # Incorrect - adds unnecessary heap entries if current_day < n: heappush(heap, (expiration_day, apples[current_day]))
3. Forgetting to Put Remaining Apples Back
Pitfall: After eating one apple from a batch, forgetting to return the remaining apples to the heap if they haven't expired yet.
Why it happens: It's intuitive to just pop from the heap and increment the counter, but each heap entry represents a batch of apples, not a single apple.
Solution:
# Correct - put remaining apples back if not expiring today if heap: expiration_day, apple_count = heappop(heap) apple_count -= 1 total_eaten += 1 if apple_count > 0 and expiration_day > current_day: heappush(heap, (expiration_day, apple_count)) # Incorrect - loses remaining apples from the batch if heap: expiration_day, apple_count = heappop(heap) total_eaten += 1 # Only counts one but discards the rest
4. Wrong Loop Termination Condition
Pitfall: Only looping through the first n
days and not continuing to eat remaining apples after the tree stops producing.
Why it happens: The natural instinct is to iterate through the input arrays, but the problem explicitly states you can continue eating after day n
.
Solution:
# Correct - continue while there are days OR apples to eat
while current_day < n or heap:
# process day...
# Incorrect - stops too early, missing apples that could be eaten after day n
for current_day in range(n):
# process day...
5. Comparison Operator Error When Removing Expired Apples
Pitfall: Using <=
instead of <
when checking for expired apples, which would incorrectly remove apples that can still be eaten today.
Why it happens: Confusion about whether the expiration day is inclusive or exclusive.
Solution:
# Correct - apples expiring on day i can still be eaten on day i while heap and heap[0][0] < current_day: heappop(heap) # Incorrect - removes apples that could be eaten today while heap and heap[0][0] <= current_day: heappop(heap)
Is the following code DFS or BFS?
void search(Node root) { if (!root) return; visit(root); root.visited = true; for (Node node in root.adjacent) { if (!node.visited) { search(node); } } }
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!