3183. The Number of Ways to Make the Sum 🔒
Problem Description
You have an unlimited supply of coins with values 1, 2, and 6, but only 2 coins with value 4.
Given an integer n
, you need to find the number of ways to make a sum equal to n
using these coins.
Key points to understand:
- Coins with values 1, 2, and 6 can be used infinitely many times
- Coins with value 4 can only be used at most twice (you have exactly 2 of them)
- The order of coins doesn't matter -
[2, 2, 3]
and[2, 3, 2]
count as the same way - Return the result modulo
10^9 + 7
since the answer can be very large
For example, if n = 4
, you could make it with:
- Four coins of value 1:
[1, 1, 1, 1]
- Two coins of value 2:
[2, 2]
- One coin of value 4:
[4]
- Two coins of value 1 and one coin of value 2:
[1, 1, 2]
The solution uses dynamic programming with a complete knapsack approach for the unlimited coins (1, 2, 6), then adds the cases where we use one or two coins of value 4.
Intuition
The key insight is to separate the problem into two parts: coins we can use unlimited times and coins with limited quantities.
First, let's think about the simpler problem - what if we only had coins of value 1, 2, and 6 (unlimited)? This is a classic complete knapsack problem. We can build up all possible sums from 0 to n
using dynamic programming. For each coin value, we check: if we can make sum j - coin_value
, then we can also make sum j
by adding this coin.
Now, what about the two coins of value 4? Here's the clever part: instead of complicating our dynamic programming with limited quantities, we can handle them separately.
Think of it this way:
- First, calculate all ways to make sum
n
using only coins 1, 2, and 6 - Then, if we use exactly one coin of value 4, we need to make the remaining sum
n - 4
using coins 1, 2, and 6 - If we use both coins of value 4, we need to make the remaining sum
n - 8
using coins 1, 2, and 6
Since we've already computed f[j]
for all values from 0 to n
(where f[j]
represents ways to make sum j
using coins 1, 2, and 6), we can simply add:
f[n]
(using no coins of value 4)f[n-4]
(using one coin of value 4, ifn >= 4
)f[n-8]
(using two coins of value 4, ifn >= 8
)
This approach elegantly sidesteps the complexity of tracking limited quantities in our DP state by treating the limited coins as special cases we handle at the end.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement the solution using dynamic programming with the complete knapsack pattern for unlimited coins, then handle the limited coins separately.
Step 1: Initialize the DP array
Create an array f
of size n + 1
where f[j]
represents the number of ways to make sum j
using coins 1, 2, and 6. Initialize f[0] = 1
since there's one way to make sum 0 (use no coins).
f = [0] * (n + 1) f[0] = 1
Step 2: Complete Knapsack for unlimited coins
For each coin value in coins = [1, 2, 6]
, iterate through all possible sums from the coin value to n
:
for x in coins:
for j in range(x, n + 1):
f[j] = (f[j] + f[j - x]) % mod
The key recurrence relation is: f[j] = f[j] + f[j - x]
f[j]
on the right represents ways to make sumj
without using coinx
f[j - x]
represents ways to make sumj - x
, to which we add coinx
to get sumj
- We apply modulo at each step to prevent overflow
Step 3: Handle the limited coins (value 4)
After computing all ways using unlimited coins, we add the cases where we use the coins of value 4:
ans = f[n] # No coins of value 4 if n >= 4: ans = (ans + f[n - 4]) % mod # Use one coin of value 4 if n >= 8: ans = (ans + f[n - 8]) % mod # Use two coins of value 4
- If
n >= 4
: We can use one coin of value 4, leavingn - 4
to be made with coins 1, 2, 6 - If
n >= 8
: We can use both coins of value 4, leavingn - 8
to be made with coins 1, 2, 6
Time Complexity: O(n)
- We iterate through the array once for each coin type, and the number of coin types is constant.
Space Complexity: O(n)
- We use a single array of size n + 1
to store the DP values.
The beauty of this approach is that it cleanly separates the unlimited and limited coin cases, avoiding the complexity of tracking quantities in the DP state while still correctly counting all possible combinations.
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 the solution with n = 6
.
Step 1: Initialize DP array
f = [1, 0, 0, 0, 0, 0, 0] ^ f[0] = 1 (one way to make 0)
Step 2: Process unlimited coins [1, 2, 6]
Processing coin 1:
- j=1: f[1] = f[1] + f[0] = 0 + 1 = 1
- j=2: f[2] = f[2] + f[1] = 0 + 1 = 1
- j=3: f[3] = f[3] + f[2] = 0 + 1 = 1
- j=4: f[4] = f[4] + f[3] = 0 + 1 = 1
- j=5: f[5] = f[5] + f[4] = 0 + 1 = 1
- j=6: f[6] = f[6] + f[5] = 0 + 1 = 1
After coin 1: f = [1, 1, 1, 1, 1, 1, 1]
(Can make any sum using only 1s)
Processing coin 2:
- j=2: f[2] = f[2] + f[0] = 1 + 1 = 2 (now we can use [1,1] or [2])
- j=3: f[3] = f[3] + f[1] = 1 + 1 = 2
- j=4: f[4] = f[4] + f[2] = 1 + 2 = 3 (can use [1,1,1,1], [2,2], or [1,1,2])
- j=5: f[5] = f[5] + f[3] = 1 + 2 = 3
- j=6: f[6] = f[6] + f[4] = 1 + 3 = 4
After coin 2: f = [1, 1, 2, 2, 3, 3, 4]
Processing coin 6:
- j=6: f[6] = f[6] + f[0] = 4 + 1 = 5
After coin 6: f = [1, 1, 2, 2, 3, 3, 5]
The 5 ways to make 6 using coins [1,2,6] are:
- [1,1,1,1,1,1]
- [1,1,1,1,2]
- [1,1,2,2]
- [2,2,2]
- [6]
Step 3: Add limited coins (value 4)
ans = f[6] = 5 # Without using any coin of value 4 # Can we use one coin of value 4? if 6 >= 4: # Yes! ans = ans + f[6-4] = 5 + f[2] = 5 + 2 = 7 # The 2 new ways: [4,1,1] and [4,2] # Can we use two coins of value 4? if 6 >= 8: # No, 6 < 8 # Skip this
Final answer: 7
The 7 total ways to make sum 6 are:
- [1,1,1,1,1,1]
- [1,1,1,1,2]
- [1,1,2,2]
- [2,2,2]
- [6]
- [1,1,4] (using one coin of value 4)
- [2,4] (using one coin of value 4)
Solution Implementation
1class Solution:
2 def numberOfWays(self, n: int) -> int:
3 # Define modulo for preventing integer overflow
4 MOD = 10**9 + 7
5
6 # Available coin denominations
7 coins = [1, 2, 6]
8
9 # dp[i] represents the number of ways to make sum i using coins
10 dp = [0] * (n + 1)
11 # Base case: one way to make sum 0 (use no coins)
12 dp[0] = 1
13
14 # Build up the dp table using coin change approach
15 # For each coin denomination
16 for coin in coins:
17 # Update all sums that can be made using this coin
18 for current_sum in range(coin, n + 1):
19 # Add ways to make (current_sum - coin) to ways to make current_sum
20 dp[current_sum] = (dp[current_sum] + dp[current_sum - coin]) % MOD
21
22 # Start with the base number of ways to make sum n
23 result = dp[n]
24
25 # Add special case: if we can make n-4, add those ways
26 # (possibly representing using 4 as a special denomination)
27 if n >= 4:
28 result = (result + dp[n - 4]) % MOD
29
30 # Add another special case: if we can make n-8, add those ways
31 # (possibly representing using 8 as a special denomination)
32 if n >= 8:
33 result = (result + dp[n - 8]) % MOD
34
35 return result
36
1class Solution {
2 public int numberOfWays(int n) {
3 // Define modulo constant for preventing integer overflow
4 final int MOD = 1_000_000_007;
5
6 // Available coin denominations
7 int[] coinValues = {1, 2, 6};
8
9 // dp[i] represents the number of ways to make sum i using the coins
10 int[] dp = new int[n + 1];
11
12 // Base case: there's exactly one way to make sum 0 (use no coins)
13 dp[0] = 1;
14
15 // Build up the dp table using unbounded knapsack approach
16 // For each coin value, update all possible sums that can include this coin
17 for (int coinValue : coinValues) {
18 for (int currentSum = coinValue; currentSum <= n; currentSum++) {
19 // Add the number of ways to make (currentSum - coinValue) to current sum
20 // This represents adding the current coin to all combinations that sum to (currentSum - coinValue)
21 dp[currentSum] = (dp[currentSum] + dp[currentSum - coinValue]) % MOD;
22 }
23 }
24
25 // Start with the base number of ways to make sum n
26 int totalWays = dp[n];
27
28 // Add special case: if we can make n-4, add those ways
29 // (possibly representing using a special 4-value coin or operation)
30 if (n >= 4) {
31 totalWays = (totalWays + dp[n - 4]) % MOD;
32 }
33
34 // Add another special case: if we can make n-8, add those ways
35 // (possibly representing using a special 8-value coin or operation)
36 if (n >= 8) {
37 totalWays = (totalWays + dp[n - 8]) % MOD;
38 }
39
40 return totalWays;
41 }
42}
43
1class Solution {
2public:
3 int numberOfWays(int n) {
4 // Define modulo constant for preventing integer overflow
5 const int MOD = 1e9 + 7;
6
7 // Available coin denominations
8 int coins[3] = {1, 2, 6};
9
10 // Dynamic programming array where dp[i] represents the number of ways to make sum i
11 int dp[n + 1];
12 memset(dp, 0, sizeof(dp));
13
14 // Base case: there's one way to make sum 0 (using no coins)
15 dp[0] = 1;
16
17 // Fill the dp array using unbounded knapsack approach
18 // For each coin denomination
19 for (int coin : coins) {
20 // Update all sums that can be formed using this coin
21 for (int sum = coin; sum <= n; ++sum) {
22 // Add the number of ways to form (sum - coin) to current sum
23 dp[sum] = (dp[sum] + dp[sum - coin]) % MOD;
24 }
25 }
26
27 // Start with the base number of ways to form sum n
28 int result = dp[n];
29
30 // Add special case: if we can use a 4-value item
31 if (n >= 4) {
32 result = (result + dp[n - 4]) % MOD;
33 }
34
35 // Add another special case: if we can use an 8-value item
36 if (n >= 8) {
37 result = (result + dp[n - 8]) % MOD;
38 }
39
40 return result;
41 }
42};
43
1/**
2 * Calculates the number of ways to reach a sum of n using specific combinations
3 * @param n - The target sum to achieve
4 * @returns The number of ways modulo 10^9 + 7
5 */
6function numberOfWays(n: number): number {
7 // Define modulo constant for large number handling
8 const MOD: number = 10 ** 9 + 7;
9
10 // Initialize dynamic programming array
11 // dp[i] represents the number of ways to reach sum i
12 const dp: number[] = Array(n + 1).fill(0);
13
14 // Base case: one way to reach sum 0 (use nothing)
15 dp[0] = 1;
16
17 // Define the base values that can be used to form the sum
18 const baseValues: number[] = [1, 2, 6];
19
20 // Calculate number of ways using base values (1, 2, 6)
21 // This is similar to coin change problem
22 for (const value of baseValues) {
23 for (let currentSum = value; currentSum <= n; currentSum++) {
24 // Add ways to reach currentSum by using 'value'
25 dp[currentSum] = (dp[currentSum] + dp[currentSum - value]) % MOD;
26 }
27 }
28
29 // Start with the base number of ways
30 let totalWays: number = dp[n];
31
32 // Add additional ways by considering special cases
33 // If we can use 4 as a combined value
34 if (n >= 4) {
35 totalWays = (totalWays + dp[n - 4]) % MOD;
36 }
37
38 // If we can use 8 as a combined value
39 if (n >= 8) {
40 totalWays = (totalWays + dp[n - 8]) % MOD;
41 }
42
43 return totalWays;
44}
45
Time and Space Complexity
The time complexity of this algorithm is O(n)
. The code uses dynamic programming with a bottom-up approach. The outer loop iterates through 3 coin denominations (constant), and for each coin, the inner loop runs from the coin value to n
. Since we have a fixed number of coins (3), the total iterations are approximately 3n
, which simplifies to O(n)
.
The space complexity is O(n)
. The algorithm creates an array f
of size n + 1
to store the number of ways to make each amount from 0 to n
. This requires O(n + 1)
space, which simplifies to O(n)
. The additional variables (mod
, coins
, ans
) use constant space and don't affect the overall space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Order of Processing Limited Coins
Pitfall: A common mistake is trying to incorporate the limited coins (value 4) directly into the dynamic programming loop alongside unlimited coins. This leads to incorrect counting because the DP state doesn't track how many times we've used the limited coins.
Incorrect Approach:
coins = [1, 2, 4, 6] # Wrong: treating 4 as unlimited
for coin in coins:
for j in range(coin, n + 1):
dp[j] = (dp[j] + dp[j - coin]) % MOD
Solution: Process unlimited and limited coins separately. First complete the DP for unlimited coins, then add contributions from limited coins:
# Step 1: Process unlimited coins
coins = [1, 2, 6]
for coin in coins:
for j in range(coin, n + 1):
dp[j] = (dp[j] + dp[j - coin]) % MOD
# Step 2: Add limited coins separately
result = dp[n]
if n >= 4:
result = (result + dp[n - 4]) % MOD
if n >= 8:
result = (result + dp[n - 8]) % MOD
2. Forgetting to Apply Modulo Operations
Pitfall: Missing modulo operations during intermediate calculations can cause integer overflow for large values of n
.
Incorrect:
dp[j] = dp[j] + dp[j - coin] # May overflow # ... result = dp[n] + dp[n - 4] + dp[n - 8] # May overflow
Solution: Apply modulo after every addition:
dp[j] = (dp[j] + dp[j - coin]) % MOD result = (result + dp[n - 4]) % MOD
3. Using 2D DP When 1D is Sufficient
Pitfall: Creating a 2D array dp[i][j]
where i
represents coins used so far and j
represents the sum. This unnecessarily increases space complexity.
Inefficient:
dp = [[0] * (n + 1) for _ in range(len(coins) + 1)]
Solution: Use a 1D array since we only need the previous state:
dp = [0] * (n + 1)
4. Incorrect Handling of Edge Cases
Pitfall: Not checking if n >= 4
or n >= 8
before accessing dp[n - 4]
or dp[n - 8]
, which could cause index out of bounds errors.
Incorrect:
result = dp[n] + dp[n - 4] + dp[n - 8] # Error if n < 8
Solution: Always check bounds before accessing array indices:
result = dp[n] if n >= 4: result = (result + dp[n - 4]) % MOD if n >= 8: result = (result + dp[n - 8]) % MOD
Which of the following is a min heap?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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
Want a Structured Path to Master System Design Too? Don’t Miss This!