Facebook Pixel

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 day i
  • 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:

  1. Add any new apples produced that day to the heap (with their expiration date)
  2. Remove any rotten apples from the heap
  3. Eat one apple from those expiring soonest (if available)
  4. Continue this process until no fresh apples remain
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Quickly give us the apple with the earliest expiration date
  2. Allow us to add new apples as they're produced
  3. 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:

  1. Initialize variables:

    • i tracks the current day (starting from day 0)
    • ans counts the total apples eaten
    • q is our min-heap priority queue
  2. 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)
  3. 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 day i rot on day i + 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
  4. 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 Evaluator

Example 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 process n days of receiving apples, then potentially continue eating for up to M more days after the last apple is received)
  • Within each iteration:
    • heappush operation when adding new apples: O(log n) - happens at most n 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 potential heappush for eating an apple: O(log n) per iteration
  • Total operations: n iterations with heap operations give us O(n × log n), plus potentially M additional iterations after day n, each with O(log n) heap operations
  • Since the heap size is bounded by n (at most n batches of apples), the overall complexity is O(n × log n + M × log n) which simplifies to O(n × log n + M) when M could be much larger than n × 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)
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More