Facebook Pixel

3610. Minimum Number of Primes to Sum to Target 🔒

Problem Description

You need to find the minimum number of prime numbers that sum up to a given integer n.

You're given two integers:

  • n: the target sum you need to achieve
  • m: you can only use the first m prime numbers

The key rules are:

  • You can only select from the first m prime numbers (e.g., if m = 3, you can only use 2, 3, and 5)
  • You can use each prime number multiple times (it's a multiset, so repetition is allowed)
  • The selected primes must sum to exactly n
  • Return the minimum count of primes needed

If it's impossible to form the sum n using the first m primes, return -1.

For example:

  • If n = 9 and m = 3, you can use primes {2, 3, 5}
  • One way: 3 + 3 + 3 = 9 (using three primes)
  • Another way: 2 + 2 + 5 = 9 (using three primes)
  • The minimum number of primes needed is 3

The solution uses dynamic programming where f[i] represents the minimum number of primes needed to sum to i. Starting from f[0] = 0, for each prime p from the first m primes, we update all possible sums by considering adding that prime: f[i] = min(f[i], f[i - p] + 1).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The problem asks for the minimum number of primes that sum to n. This is a classic optimization problem that resembles the coin change problem - we want to make change for n using the fewest "coins" (primes).

Think about building up to the target sum n incrementally. For any number i, the minimum primes needed to reach it depends on the minimum primes needed to reach smaller numbers. If we already know the minimum primes to reach i - p (where p is a prime we can use), then we can reach i by adding one more prime p.

This naturally leads to a dynamic programming approach:

  • Start with the base case: we need 0 primes to sum to 0
  • For each number from 1 to n, calculate the minimum primes needed
  • For each position i, try adding each available prime p to a previous sum i - p

The recurrence relation becomes: f[i] = min(f[i], f[i - p] + 1) for each prime p we can use.

Why does this work? Because we're systematically considering all possible ways to form each sum, keeping only the minimum count. By building from smaller sums to larger ones, we ensure that when we calculate f[i], we already have the optimal solutions for all smaller values.

The preprocessing step generates the first 1000 primes upfront because we might need up to the m-th prime, and having them ready avoids recalculating during the main algorithm.

Solution Approach

The solution consists of two main parts: preprocessing prime numbers and applying dynamic programming.

Step 1: Preprocessing Prime Numbers

First, we generate the first 1000 prime numbers using a simple primality testing algorithm:

  • Start with x = 2 and an empty list of primes
  • For each number x, check if it's divisible by any previously found prime p where p * p ≤ x
  • If no prime divides x, then x is prime and add it to the list
  • Continue until we have 1000 primes
primes = []
x = 2
while len(primes) < 1000:
    is_prime = True
    for p in primes:
        if p * p > x:
            break
        if x % p == 0:
            is_prime = False
            break
    if is_prime:
        primes.append(x)
    x += 1

Step 2: Dynamic Programming

We use a DP array f where f[i] represents the minimum number of primes needed to sum to i.

Initialization:

  • f[0] = 0 (zero primes sum to 0)
  • f[i] = inf for all other i from 1 to n (initially impossible)

State Transition: For each prime x from the first m primes and for each sum i from x to n:

f[i] = min(f[i], f[i - x] + 1)

This means: to reach sum i, we can either:

  • Keep the current minimum f[i]
  • Or use one prime x plus the minimum primes needed for i - x

Implementation Details:

def minNumberOfPrimes(self, n: int, m: int) -> int:
    f = [0] + [inf] * n  # f[0] = 0, others = inf
  
    for x in primes[:m]:  # Use only first m primes
        for i in range(x, n + 1):  # Update sums from x to n
            f[i] = min(f[i], f[i - x] + 1)
  
    return f[n] if f[n] < inf else -1

The outer loop iterates through each available prime, and the inner loop updates all possible sums that can include this prime. After processing all primes, f[n] contains the minimum number of primes needed to sum to n, or remains inf if it's impossible.

Time Complexity: O(m * n) where m is the number of available primes and n is the target sum. Space Complexity: O(n) for the DP array.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example where n = 10 and m = 3, meaning we need to sum to 10 using only the first 3 prime numbers: {2, 3, 5}.

Initial Setup:

  • First 3 primes: [2, 3, 5]
  • DP array f of size 11 (indices 0 to 10)
  • Initialize: f = [0, inf, inf, inf, inf, inf, inf, inf, inf, inf, inf]

Processing Prime 2: Starting from index 2, we update each position by considering adding prime 2:

  • f[2] = min(inf, f[0] + 1) = min(inf, 0 + 1) = 1 (one 2 makes 2)
  • f[3] = min(inf, f[1] + 1) = min(inf, inf + 1) = inf (can't make 3 yet)
  • f[4] = min(inf, f[2] + 1) = min(inf, 1 + 1) = 2 (two 2s make 4)
  • f[5] = min(inf, f[3] + 1) = min(inf, inf + 1) = inf
  • f[6] = min(inf, f[4] + 1) = min(inf, 2 + 1) = 3 (three 2s make 6)
  • f[7] = min(inf, f[5] + 1) = min(inf, inf + 1) = inf
  • f[8] = min(inf, f[6] + 1) = min(inf, 3 + 1) = 4 (four 2s make 8)
  • f[9] = min(inf, f[7] + 1) = min(inf, inf + 1) = inf
  • f[10] = min(inf, f[8] + 1) = min(inf, 4 + 1) = 5 (five 2s make 10)

After prime 2: f = [0, inf, 1, inf, 2, inf, 3, inf, 4, inf, 5]

Processing Prime 3: Starting from index 3, we update positions considering prime 3:

  • f[3] = min(inf, f[0] + 1) = min(inf, 0 + 1) = 1 (one 3 makes 3)
  • f[4] = min(2, f[1] + 1) = min(2, inf + 1) = 2 (stays as 2)
  • f[5] = min(inf, f[2] + 1) = min(inf, 1 + 1) = 2 (2 + 3 = 5)
  • f[6] = min(3, f[3] + 1) = min(3, 1 + 1) = 2 (3 + 3 = 6)
  • f[7] = min(inf, f[4] + 1) = min(inf, 2 + 1) = 3 (2 + 2 + 3 = 7)
  • f[8] = min(4, f[5] + 1) = min(4, 2 + 1) = 3 (2 + 3 + 3 = 8)
  • f[9] = min(inf, f[6] + 1) = min(inf, 2 + 1) = 3 (3 + 3 + 3 = 9)
  • f[10] = min(5, f[7] + 1) = min(5, 3 + 1) = 4 (2 + 2 + 3 + 3 = 10)

After prime 3: f = [0, inf, 1, 1, 2, 2, 2, 3, 3, 3, 4]

Processing Prime 5: Starting from index 5, we update positions considering prime 5:

  • f[5] = min(2, f[0] + 1) = min(2, 0 + 1) = 1 (one 5 makes 5)
  • f[6] = min(2, f[1] + 1) = min(2, inf + 1) = 2 (stays as 2)
  • f[7] = min(3, f[2] + 1) = min(3, 1 + 1) = 2 (2 + 5 = 7)
  • f[8] = min(3, f[3] + 1) = min(3, 1 + 1) = 2 (3 + 5 = 8)
  • f[9] = min(3, f[4] + 1) = min(3, 2 + 1) = 3 (stays as 3)
  • f[10] = min(4, f[5] + 1) = min(4, 1 + 1) = 2 (5 + 5 = 10)

Final DP array: f = [0, inf, 1, 1, 2, 1, 2, 2, 2, 3, 2]

Result: f[10] = 2, meaning the minimum number of primes needed to sum to 10 is 2 (which can be achieved with 5 + 5).

Solution Implementation

1# Pre-compute first 1000 prime numbers using Sieve of Eratosthenes approach
2primes = []
3x = 2
4M = 1000
5while len(primes) < M:
6    is_prime = True
7    # Check if x is divisible by any prime found so far
8    for p in primes:
9        # Optimization: only check primes up to sqrt(x)
10        if p * p > x:
11            break
12        if x % p == 0:
13            is_prime = False
14            break
15    if is_prime:
16        primes.append(x)
17    x += 1
18
19
20class Solution:
21    def minNumberOfPrimes(self, n: int, m: int) -> int:
22        """
23        Find minimum number of primes (from first m primes) that sum to n.
24        Uses dynamic programming approach similar to coin change problem.
25      
26        Args:
27            n: target sum
28            m: number of primes to consider (from the pre-computed list)
29      
30        Returns:
31            Minimum number of primes needed, or -1 if impossible
32        """
33        # Initialize DP array: f[i] = minimum primes needed to sum to i
34        # f[0] = 0 (base case: 0 primes needed for sum 0)
35        # f[i] = inf initially for i > 0 (not yet reachable)
36        f = [0] + [float('inf')] * n
37      
38        # Consider each prime from the first m primes
39        for prime in primes[:m]:
40            # Update all sums that can be reached by adding this prime
41            for i in range(prime, n + 1):
42                # f[i] = min(current value, value if we use this prime)
43                f[i] = min(f[i], f[i - prime] + 1)
44      
45        # Return result: minimum primes for sum n, or -1 if impossible
46        return f[n] if f[n] < float('inf') else -1
47
1class Solution {
2    // Static list to store prime numbers
3    private static List<Integer> primes = new ArrayList<>();
4  
5    // Static block to initialize the first 1000 prime numbers
6    static {
7        int candidate = 2;
8        final int MAX_PRIMES = 1000;
9      
10        while (primes.size() < MAX_PRIMES) {
11            boolean isPrime = true;
12          
13            // Check if candidate is divisible by any previously found prime
14            for (int prime : primes) {
15                // Optimization: only check primes up to sqrt(candidate)
16                if (prime * prime > candidate) {
17                    break;
18                }
19                // If divisible, it's not prime
20                if (candidate % prime == 0) {
21                    isPrime = false;
22                    break;
23                }
24            }
25          
26            // Add to primes list if it's prime
27            if (isPrime) {
28                primes.add(candidate);
29            }
30          
31            candidate++;
32        }
33    }
34
35    /**
36     * Finds the minimum number of prime numbers that sum to n
37     * @param n the target sum
38     * @param m the number of smallest primes to use
39     * @return minimum number of primes needed, or -1 if impossible
40     */
41    public int minNumberOfPrimes(int n, int m) {
42        // Dynamic programming array where dp[i] represents minimum primes needed to sum to i
43        int[] dp = new int[n + 1];
44        final int INFINITY = 1 << 30;
45      
46        // Initialize all values to infinity (impossible)
47        Arrays.fill(dp, INFINITY);
48      
49        // Base case: 0 requires 0 primes
50        dp[0] = 0;
51      
52        // Use only the first m prime numbers
53        for (int prime : primes.subList(0, m)) {
54            // Update dp array for all values that can be formed using this prime
55            for (int i = prime; i <= n; i++) {
56                // Either keep current value or use one more prime
57                dp[i] = Math.min(dp[i], dp[i - prime] + 1);
58            }
59        }
60      
61        // Return result: minimum primes needed or -1 if impossible
62        return dp[n] < INFINITY ? dp[n] : -1;
63    }
64}
65
1class Solution {
2public:
3    int minNumberOfPrimes(int n, int m) {
4        // Static cache for prime numbers to avoid recalculation across multiple calls
5        static vector<int> primeCache;
6      
7        // Generate prime numbers if cache is empty
8        if (primeCache.empty()) {
9            const int MAX_PRIMES = 1000;
10            int candidate = 2;
11          
12            // Generate MAX_PRIMES prime numbers using trial division
13            while (static_cast<int>(primeCache.size()) < MAX_PRIMES) {
14                bool isPrime = true;
15              
16                // Check if candidate is divisible by any previously found prime
17                for (int prime : primeCache) {
18                    // Optimization: only check primes up to sqrt(candidate)
19                    if (prime * prime > candidate) {
20                        break;
21                    }
22                  
23                    if (candidate % prime == 0) {
24                        isPrime = false;
25                        break;
26                    }
27                }
28              
29                // Add to prime cache if it's prime
30                if (isPrime) {
31                    primeCache.push_back(candidate);
32                }
33              
34                candidate++;
35            }
36        }
37      
38        // Dynamic programming array where dp[i] represents minimum primes needed to sum to i
39        vector<int> dp(n + 1, INT_MAX);
40        dp[0] = 0;  // Base case: 0 primes needed to sum to 0
41      
42        // Use only the first m primes from our cache
43        vector<int> availablePrimes(primeCache.begin(), primeCache.begin() + m);
44      
45        // For each available prime number
46        for (int prime : availablePrimes) {
47            // Update all possible sums that can be formed using this prime
48            for (int currentSum = prime; currentSum <= n; ++currentSum) {
49                // If we can reach (currentSum - prime), we can reach currentSum
50                if (dp[currentSum - prime] != INT_MAX) {
51                    dp[currentSum] = min(dp[currentSum], dp[currentSum - prime] + 1);
52                }
53            }
54        }
55      
56        // Return the result: minimum primes needed for n, or -1 if impossible
57        return (dp[n] < INT_MAX) ? dp[n] : -1;
58    }
59};
60
1// Generate first M prime numbers using Sieve-like approach
2const primes: number[] = [];
3let candidate = 2;
4const MAX_PRIMES = 1000;
5
6// Build array of prime numbers
7while (primes.length < MAX_PRIMES) {
8    let isPrime = true;
9  
10    // Check if candidate is divisible by any previously found prime
11    for (const prime of primes) {
12        // Optimization: only check primes up to sqrt(candidate)
13        if (prime * prime > candidate) break;
14      
15        // If divisible, it's not prime
16        if (candidate % prime === 0) {
17            isPrime = false;
18            break;
19        }
20    }
21  
22    // Add to primes array if it passed all divisibility tests
23    if (isPrime) {
24        primes.push(candidate);
25    }
26  
27    candidate++;
28}
29
30/**
31 * Find minimum number of primes needed to sum to n
32 * Uses dynamic programming approach similar to coin change problem
33 * 
34 * @param n - Target sum to achieve
35 * @param m - Number of primes to use (from the first m primes)
36 * @returns Minimum number of primes needed, or -1 if impossible
37 */
38function minNumberOfPrimes(n: number, m: number): number {
39    const INFINITY = 1e9;
40  
41    // dp[i] represents minimum number of primes needed to sum to i
42    const dp: number[] = Array(n + 1).fill(INFINITY);
43  
44    // Base case: 0 primes needed to sum to 0
45    dp[0] = 0;
46  
47    // Consider first m primes
48    for (const prime of primes.slice(0, m)) {
49        // Update all possible sums that can be formed by adding current prime
50        for (let sum = prime; sum <= n; sum++) {
51            // If we can reach (sum - prime), we can reach sum by adding current prime
52            if (dp[sum - prime] < INFINITY) {
53                dp[sum] = Math.min(dp[sum], dp[sum - prime] + 1);
54            }
55        }
56    }
57  
58    // Return result if reachable, otherwise -1
59    return dp[n] < INFINITY ? dp[n] : -1;
60}
61

Time and Space Complexity

The code consists of two parts: prime number generation and the dynamic programming solution.

Prime Generation Part:

  • Time: O(M × √M) where M = 1000. For each number checked, we iterate through existing primes up to its square root.
  • Space: O(M) to store the primes list.

Dynamic Programming Part:

  • Time: O(m × n). The outer loop iterates through the first m primes, and for each prime x, the inner loop iterates from x to n+1, which is approximately n iterations. Therefore, the total time complexity is O(m × n).
  • Space: O(n) for the DP array f of size n+1.

Overall Complexity:

  • Time Complexity: O(M × √M + m × n). Since M is a constant (1000) and the prime generation happens only once, the dominant term is O(m × n).
  • Space Complexity: O(n + M), where n is for the DP array and M is for storing the preprocessed primes.

Common Pitfalls

1. Incorrect DP Array Update Order

A common mistake is updating the DP array in the wrong direction or using a 1D array incorrectly, which can lead to using the same prime multiple times unintentionally in a single iteration.

Incorrect approach:

# WRONG: This might count the same prime multiple times incorrectly
for prime in primes[:m]:
    for i in range(n, prime - 1, -1):  # Backward iteration
        f[i] = min(f[i], f[i - prime] + 1)

This backward iteration pattern is used for 0/1 knapsack problems where each item can only be used once. Since we can use each prime multiple times (unbounded knapsack), we need forward iteration.

Correct approach:

# CORRECT: Forward iteration allows using each prime multiple times
for prime in primes[:m]:
    for i in range(prime, n + 1):  # Forward iteration
        f[i] = min(f[i], f[i - prime] + 1)

2. Prime Generation Overflow or Inefficiency

When generating primes, using an inefficient algorithm or not optimizing the primality check can cause performance issues.

Inefficient approach:

# INEFFICIENT: Checking divisibility by all numbers
for divisor in range(2, x):
    if x % divisor == 0:
        is_prime = False

Optimized approach:

# EFFICIENT: Only check previously found primes up to sqrt(x)
for p in primes:
    if p * p > x:
        break
    if x % p == 0:
        is_prime = False
        break

3. Not Handling Edge Cases

Failing to handle special cases like n = 0 or m = 0 can cause errors.

Solution:

def minNumberOfPrimes(self, n: int, m: int) -> int:
    # Handle edge cases
    if n == 0:
        return 0
    if m == 0:
        return -1
  
    # Rest of the DP solution...

4. Integer Overflow with Large Values

Using a sentinel value that's too small instead of infinity can cause incorrect comparisons.

Problematic approach:

# WRONG: Using a fixed large number that might not be large enough
f = [0] + [10000] * n  # What if the answer is legitimately > 10000?

Correct approach:

# CORRECT: Using float('inf') ensures proper comparison
f = [0] + [float('inf')] * n

5. Off-by-One Errors in Array Indexing

Not properly handling array bounds when initializing or accessing the DP array.

Common mistake:

# WRONG: Missing the last element
f = [float('inf')] * n  # Should be n+1 elements
for i in range(prime, n):  # Should go up to n inclusive

Correct approach:

# CORRECT: Proper array size and iteration bounds
f = [0] + [float('inf')] * n  # n+1 elements total
for i in range(prime, n + 1):  # Include n in the range
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?


Recommended Readings

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

Load More