Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1553. Minimum Number of Days to Eat N Oranges - Leetcode Solution

Code Implementation

class Solution:
    def minDays(self, n: int) -> int:
        from functools import lru_cache

        @lru_cache(None)
        def dp(x):
            if x == 0:
                return 0
            if x == 1:
                return 1
            # Option 1: Eat one orange
            # Option 2: Eat x//2 oranges if x % 2 == 0
            # Option 3: Eat 2*x//3 oranges if x % 3 == 0
            # For option 2 and 3, we may need to eat some oranges one by one to reach a divisible number
            return 1 + min(
                x % 2 + dp(x // 2),
                x % 3 + dp(x // 3)
            )
        return dp(n)
      
#include <unordered_map>
class Solution {
public:
    std::unordered_map<int, int> memo;
    int minDays(int n) {
        if (n == 0) return 0;
        if (n == 1) return 1;
        if (memo.count(n)) return memo[n];
        int eatOne = 1 + minDays(n - 1);
        int eatHalf = 1 + (n % 2) + minDays(n / 2);
        int eatTwoThirds = 1 + (n % 3) + minDays(n / 3);
        int res = std::min(eatHalf, eatTwoThirds);
        memo[n] = std::min(eatOne, res);
        return memo[n];
    }
};
      
import java.util.*;

class Solution {
    private Map<Integer, Integer> memo = new HashMap<>();

    public int minDays(int n) {
        if (n == 0) return 0;
        if (n == 1) return 1;
        if (memo.containsKey(n)) return memo.get(n);
        int eatOne = 1 + minDays(n - 1);
        int eatHalf = 1 + (n % 2) + minDays(n / 2);
        int eatTwoThirds = 1 + (n % 3) + minDays(n / 3);
        int res = Math.min(eatHalf, eatTwoThirds);
        int ans = Math.min(eatOne, res);
        memo.put(n, ans);
        return ans;
    }
}
      
var minDays = function(n) {
    const memo = new Map();

    function dp(x) {
        if (x === 0) return 0;
        if (x === 1) return 1;
        if (memo.has(x)) return memo.get(x);
        let eatHalf = 1 + (x % 2) + dp(Math.floor(x / 2));
        let eatTwoThirds = 1 + (x % 3) + dp(Math.floor(x / 3));
        let res = Math.min(eatHalf, eatTwoThirds);
        memo.set(x, res);
        return res;
    }
    return dp(n);
};
      

Problem Description

Given an integer n representing the number of oranges you have, you want to eat all of them in the minimum number of days. Each day, you can choose one of the following actions:

  • Eat one orange.
  • If n is divisible by 2, eat n / 2 oranges in one day.
  • If n is divisible by 3, eat 2 * (n / 3) oranges in one day (i.e., leave n / 3 oranges).

Find the minimum number of days required to eat all n oranges. Each orange can only be eaten once, and you must choose one action per day.

Thought Process

At first glance, the problem seems to ask for a simple greedy solution: always take the largest possible chunk (either by 2 or by 3) if possible. However, this does not always yield the fewest days, because sometimes eating one orange to reach a divisible state can be optimal.

Brute-force recursion tries all possible choices at each step, but quickly becomes infeasible for large n due to repeated subproblems. To optimize, we realize that the problem has overlapping subproblems and optimal substructure, making it a perfect candidate for memoization (dynamic programming).

The key is to minimize the number of days by considering, at each state:

  • Eating one orange, reducing n by 1.
  • Eating n/2 oranges if n is divisible by 2, possibly after eating a few one by one to reach divisibility.
  • Eating 2*(n/3) oranges if n is divisible by 3, possibly after eating a few one by one to reach divisibility.
By storing results for each n in a hash map, we avoid redundant calculations.

Solution Approach

We solve the problem using recursion with memoization (top-down dynamic programming). The main idea is to define a function dp(n) that returns the minimum number of days to eat n oranges.

  1. Base Cases:
    • If n == 0, return 0 (no oranges left).
    • If n == 1, return 1 (one day to eat the last orange).
  2. Recursive Choices:
    • Option 1: Eat one orange, so 1 + dp(n - 1).
    • Option 2: If n is divisible by 2, eat half. But if not, eat n % 2 one by one until it is, then eat half: n % 2 + 1 + dp(n // 2).
    • Option 3: If n is divisible by 3, eat two thirds. If not, eat n % 3 one by one until it is, then eat two thirds: n % 3 + 1 + dp(n // 3).
  3. Memoization: Use a hash map (or built-in cache) to store already computed results for each n to avoid recomputation.
  4. Return: For each n, return the minimum of the three options above.

This approach ensures we always find the optimal (minimum days) path, and by caching results, we vastly reduce the time complexity.

Example Walkthrough

Let's walk through the example where n = 10:

  1. Step 1: n = 10
    • Option 1: Eat one orange (n = 9)
    • Option 2: n % 2 == 0, so eat half (n = 5)
    • Option 3: n % 3 == 1, so eat one orange to get to n = 9, then eat two thirds
  2. Let's try Option 2 (eat half): n = 5
    • Option 1: Eat one (n = 4)
    • Option 2: n % 2 == 1, eat one to get to n = 4, then eat half (n = 2)
    • Option 3: n % 3 == 2, eat two to get to n = 3, then eat two thirds (n = 1)
    The optimal path for n = 5 is:
    • Eat two to get to n = 3, then eat two thirds (n = 1), then eat the last orange. Total = 2 (to n=3) + 1 (two thirds) + 1 (last orange) = 4 days.
  3. Back to n = 10, Option 2: 1 (eat half) + 4 (minDays(5)) = 5 days
  4. Trying Option 1: Eat one (n = 9), then repeat similar process.
  5. The algorithm tries all paths and picks the minimum. For n = 10, the minimum is 4 days.

This example shows how the recursive approach explores all possibilities and always takes the path with the fewest days, even if it means eating one orange at a time to reach a better chunk.

Time and Space Complexity

  • Brute-force Approach: Exponential time, since each state can branch into up to three subproblems, and many states are recomputed. This is infeasible for large n.
  • Optimized (Memoized) Approach:
    • Each unique n is computed only once and stored. Since each recursive call reduces n by at least a fraction (by 2 or 3), the number of unique states is at most O(n).
    • Each call does O(1) work (arithmetic and hash map lookup), so total time is O(n).
    • Space complexity is O(n) for the memoization table and recursion stack.

Summary

The problem asks for the minimum number of days to eat all n oranges, where each day you can eat one orange, half (if divisible by 2), or two-thirds (if divisible by 3). A naive greedy approach does not always yield the best result, so we use recursion with memoization to try all possible choices at each step, caching results for efficiency. This dynamic programming solution ensures optimality and drastically reduces computation time, making it elegant and efficient for large inputs.