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 achievem
: you can only use the firstm
prime numbers
The key rules are:
- You can only select from the first
m
prime numbers (e.g., ifm = 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
andm = 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)
.
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 primep
to a previous sumi - 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 primep
wherep * p ≤ x
- If no prime divides
x
, thenx
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 otheri
from 1 ton
(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 fori - 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 EvaluatorExample 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)
whereM = 1000
. For each number checked, we iterate through existing primes up to its square root. - Space:
O(M)
to store theprimes
list.
Dynamic Programming Part:
- Time:
O(m × n)
. The outer loop iterates through the firstm
primes, and for each primex
, the inner loop iterates fromx
ton+1
, which is approximatelyn
iterations. Therefore, the total time complexity isO(m × n)
. - Space:
O(n)
for the DP arrayf
of sizen+1
.
Overall Complexity:
- Time Complexity:
O(M × √M + m × n)
. SinceM
is a constant (1000) and the prime generation happens only once, the dominant term isO(m × n)
. - Space Complexity:
O(n + M)
, wheren
is for the DP array andM
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
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!