LeetCode 3183 - The Number of Ways to Make the Sum
The problem asks us to determine the number of distinct ways to sum up to an integer n using a limited set of coins. Specifically, we have coins of value 1, 2, and 6 available in infinite quantities, and coins of value 4 with a strict limit of two.
Difficulty: 🟡 Medium
Topics: Array, Dynamic Programming
Solution
Problem Understanding
The problem asks us to determine the number of distinct ways to sum up to an integer n using a limited set of coins. Specifically, we have coins of value 1, 2, and 6 available in infinite quantities, and coins of value 4 with a strict limit of two. The key points are that the order of coins does not matter, so [1, 2, 1] is considered the same as [2, 1, 1], and the final answer must be computed modulo $10^9 + 7$ because the result could be very large.
The input n represents the target sum we want to form, and it ranges from 1 to 100,000, indicating that brute-force enumeration of all combinations will be inefficient. Edge cases include small values of n (such as 1, 2, 3) where limited coins are used, and values of n that exceed the total value of available 4-coins (greater than 8), which must be handled correctly.
The problem is essentially a variation of the bounded coin change problem, where one of the coin types has a usage limit, and the rest are unlimited.
Approaches
Brute Force
A naive brute-force approach would be to recursively generate all possible combinations of coins, taking care not to exceed the allowed number of 4-coins, and count those that sum to n. This approach is correct in principle because it enumerates all valid combinations. However, the recursion would have a very large branching factor due to infinite coins of 1, 2, and 6, making it exponential in time and infeasible for large n (up to 100,000).
Optimal Approach
The optimal solution uses dynamic programming. The main idea is to maintain a dp array where dp[x] represents the number of ways to make sum x using the available coins. We handle coins differently based on their availability:
- Coins of 1, 2, and 6 are unbounded, so we can use the classic unbounded knapsack update:
dp[i] += dp[i - coin]. - Coin of 4 is bounded, with at most 2 coins, so we must limit the number of times we add this coin. We do this by iterating the count of 4-coins from 1 up to 2 and updating the DP array in reverse to avoid double counting.
This approach transforms the problem into a standard DP problem with careful handling for bounded and unbounded coins. It has a complexity linear in n multiplied by the number of coin types.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(?) | O(?) | Exponential recursion enumerating all valid sums |
| Dynamic Programming | O(n * c) | O(n) | c is number of coins (here, 4). Handles bounded and unbounded coins correctly |
Algorithm Walkthrough
- Initialize a
dparray of sizen+1with all elements zero. Setdp[0] = 1because there is exactly one way to form sum 0: use no coins. - Iterate over the unbounded coins
[1, 2, 6]. For each coinc, iterateifromcton, and updatedp[i] += dp[i - c] % MOD. This accumulates all ways to formiusing unlimited 1, 2, and 6 coins. - Handle the bounded coin 4 separately. For
countfrom 1 to 2, iterateifromndown tocount * 4, and updatedp[i] += dp[i - 4 * count] % MOD. Reverse iteration prevents overcounting combinations using fewer 4-coins multiple times. - Return
dp[n] % MOD.
Why it works: The DP invariant is that after processing a coin, dp[i] represents the total number of ways to form sum i using all coins processed so far. By separating bounded and unbounded coins and iterating appropriately, we ensure all valid combinations are counted exactly once.
The problem asks us to count how many distinct combinations of coins can produce a target sum n.
We are given an unlimited number of coins with values 1, 2, and 6, but we are restricted to using at most two coins with value 4. The important detail is that combinations are unordered. This means that [1,2,1] and [2,1,1] represent the same combination and should only be counted once.
The input is a single integer n, representing the desired target sum. The output is the number of valid combinations that add up exactly to n, modulo 10^9 + 7.
The constraint 1 <= n <= 10^5 is large enough that exponential or deeply recursive brute-force solutions will not work. We need an algorithm that runs in approximately linear time.
The presence of unlimited coins immediately resembles the classic Coin Change II dynamic programming problem. However, there is one special restriction: the coin 4 can only be used at most twice. That restriction prevents us from using the standard unbounded coin DP directly for all coins.
Several edge cases are important:
- Small values such as
n = 1orn = 2, where only a few combinations exist. - Values where using two
4s exactly reaches the target. - Cases where using three
4s would normally be possible, but must be excluded. - Large values near
10^5, where efficiency becomes critical. - Targets smaller than
4, where the restricted coin cannot participate at all.
Approaches
Brute Force Approach
A brute-force solution would attempt to generate every possible combination of coins whose sum equals n.
One way to do this is recursive backtracking. At each step, we choose whether to use coin 1, 2, 4, or 6, while carefully enforcing the rule that coin 4 can appear at most twice. To avoid duplicate permutations, we would process coins in sorted order.
This approach is correct because it systematically explores every valid combination exactly once. However, the number of possible combinations grows very quickly as n increases. Since n can be as large as 100000, exhaustive enumeration becomes completely impractical.
The recursion tree becomes enormous, leading to exponential time complexity.
Optimal Dynamic Programming Approach
The key observation is that this is fundamentally a coin combination counting problem, which is a classic dynamic programming pattern.
For unrestricted coins 1, 2, and 6, we can use the standard unbounded knapsack transition:
$$dp[s] += dp[s - coin]$$
The challenge is handling coin 4, which is limited to two uses.
Instead of trying to incorporate a bounded coin directly into the DP transitions, we can separate the problem into cases:
- Use zero
4s - Use one
4 - Use two
4s
For each case, we reduce the remaining target and count the number of ways to form it using only unrestricted coins 1, 2, and 6.
If:
ways[x]= number of ways to form sumxusing only1,2, and6
then the final answer becomes:
$$ways[n] + ways[n-4] + ways[n-8]$$
where terms with negative indices are ignored.
This works because:
- Choosing how many
4s to use uniquely determines the remaining sum. - The remaining sum is formed entirely from unrestricted coins.
- Each valid combination belongs to exactly one of these three categories.
This transforms the problem into a simple linear dynamic programming computation.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(4^n) | O(n) | Recursively explores all combinations |
| Optimal | O(n) | O(n) | Dynamic programming with case separation for coin 4 |
Algorithm Walkthrough
- Create a DP array
dpof sizen + 1.
The value dp[s] will represent the number of ways to form sum s using only the unrestricted coins 1, 2, and 6.
2. Initialize the base case.
Set:
dp[0] = 1
There is exactly one way to form sum 0, choose no coins.
3. Process each unrestricted coin.
Iterate through coins [1, 2, 6].
For each coin, update all reachable sums from coin up to n.
4. Apply the unbounded coin transition.
For every sum s:
$$dp[s] += dp[s - coin]$$
This works because if we already know how many ways can form s - coin, then appending the current coin creates valid combinations for s.
5. Compute the final answer using the limited 4 coin cases.
Since we may use:
- zero
4s - one
4 - two
4s
the answer becomes:
$$dp[n] + dp[n-4] + dp[n-8]$$
whenever those indices are non-negative. 6. Apply modulo arithmetic.
Since the number of combinations can become very large, take every update modulo:
$$10^9 + 7$$
Why it works
The DP correctly counts all combinations using unrestricted coins because each coin is processed in ascending order, which avoids counting permutations multiple times.
Separating the restricted 4 coin into three explicit cases guarantees correctness because every valid combination must use either zero, one, or two 4s. These categories are disjoint, so summing their counts produces the exact total number of valid combinations.
Python Solution
class Solution:
def numberOfWays(self, n: int) -> int:
MOD = 10**9 + 7
dp = [0] * (n + 1)
dp[0] = 1
# Unbounded coins
for coin in [1, 2, 6]:
for i in range(coin, n + 1):
dp[i] = (dp[i] + dp[i - coin]) % MOD
# Bounded coin 4 (max 2 coins)
for count in range(1, 3):
for i in range(n, 4 * count - 1, -1):
dp[i] = (dp[i] + dp[i - 4 * count]) % MOD
return dp[n]
The Python implementation first fills the DP table with unbounded coins. Then, it correctly handles the 4-coin using reverse iteration to account for the bounded limit. Modulo operations are applied throughout to prevent integer overflow.
dp = [0] * (n + 1)
dp[0] = 1
for coin in [1, 2, 6]:
for current_sum in range(coin, n + 1):
dp[current_sum] = (
dp[current_sum] + dp[current_sum - coin]
) % MOD
answer = dp[n]
if n >= 4:
answer = (answer + dp[n - 4]) % MOD
if n >= 8:
answer = (answer + dp[n - 8]) % MOD
return answer
The implementation begins by creating a DP array where each index stores the number of ways to form that sum using only coins `1`, `2`, and `6`.
The base case `dp[0] = 1` is essential because it represents the empty combination. Without it, no future states could be built correctly.
The nested loops implement the standard unbounded coin change transition. Processing coins one at a time ensures combinations are counted rather than permutations.
After computing all unrestricted combinations, the solution explicitly handles the limited `4` coin cases:
- `dp[n]` represents using zero `4`s.
- `dp[n - 4]` represents using one `4`.
- `dp[n - 8]` represents using two `4`s.
These values are added together to produce the final answer.
## Go Solution
```go
func numberOfWays(n int) int {
MOD := int(1e9 + 7)
dp := make([]int, n+1)
dp[0] = 1
// Unbounded coins
coins := []int{1, 2, 6}
for _, coin := range coins {
for i := coin; i <= n; i++ {
dp[i] = (dp[i] + dp[i-coin]) % MOD
}
}
// Bounded coin 4 (max 2 coins)
for count := 1; count <= 2; count++ {
for i := n; i >= 4*count; i-- {
dp[i] = (dp[i] + dp[i-4*count]) % MOD
}
}
return dp[n]
}
The Go implementation mirrors the Python logic. Slice initialization and reverse iteration for bounded coins are handled explicitly. Modulo arithmetic ensures correctness for large sums.
Worked Examples
Example 1: n = 4
Initial dp:
| i | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| dp | 1 | 0 | 0 | 0 | 0 |
After unbounded coins [1, 2, 6]:
- coin = 1 → dp = [1,1,1,1,1]
- coin = 2 → dp = [1,1,2,2,3]
- coin = 6 → no effect
After bounded coin 4:
- count = 1 → dp[4] += dp[0] → dp[4] = 4
- count = 2 → i=4 too small, no effect
Output: 4
Example 2: n = 5
- Unbounded coins → dp = [1,1,2,2,3,4]
- Bounded coin 4 → count=1 → dp[5] += dp[1] → dp[5]=4, count=2 → no effect
Output: 4
Example 3: n = 12
Computation proceeds similarly. After all iterations, dp[12] = 22. const MOD int = 1e9 + 7
dp := make([]int, n+1)
dp[0] = 1
coins := []int{1, 2, 6}
for _, coin := range coins {
for currentSum := coin; currentSum <= n; currentSum++ {
dp[currentSum] =
(dp[currentSum] + dp[currentSum-coin]) % MOD
}
}
answer := dp[n]
if n >= 4 {
answer = (answer + dp[n-4]) % MOD
}
if n >= 8 {
answer = (answer + dp[n-8]) % MOD
}
return answer
}
The Go implementation follows exactly the same logic as the Python solution.
The primary difference is slice handling. In Go, the DP array is created using `make([]int, n+1)`.
Modulo arithmetic is also explicitly handled using integer operations to avoid overflow issues. Since Go's `int` type is large enough for intermediate computations under the modulus, no additional casting is necessary.
## Worked Examples
### Example 1
Input:
n = 4
We first compute combinations using only `1`, `2`, and `6`.
#### After processing coin 1
| Sum | dp |
| --- | --- |
| 0 | 1 |
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
Only all-ones combinations exist.
#### After processing coin 2
| Sum | dp |
| --- | --- |
| 0 | 1 |
| 1 | 1 |
| 2 | 2 |
| 3 | 2 |
| 4 | 3 |
The combinations for `4` are now:
- `1+1+1+1`
- `1+1+2`
- `2+2`
#### After processing coin 6
No changes occur because `6 > 4`.
Now evaluate restricted `4` usage:
- Zero `4`s: `dp[4] = 3`
- One `4`: `dp[0] = 1`
- Two `4`s: impossible
Final answer:
3 + 1 = 4
### Example 2
Input:
n = 12
After processing unrestricted coins:
| Sum | dp |
| --- | --- |
| 12 | 12 |
| 8 | 7 |
| 4 | 3 |
Now evaluate cases:
- Zero `4`s: `12`
- One `4`: `7`
- Two `4`s: `3`
Final answer:
12 + 7 + 3 = 22
This correctly excludes the invalid combination `[4,4,4]`.
### Example 3
Input:
n = 5
Unrestricted combinations for `5`:
- `1+1+1+1+1`
- `1+1+1+2`
- `1+2+2`
Thus:
dp[5] = 3
Now evaluate `4` usage:
- Zero `4`s: `3`
- One `4`: `dp[1] = 1`
- Two `4`s: impossible
Final answer:
3 + 1 = 4
## Complexity Analysis
| Measure | Complexity | Explanation |
| --- | --- | --- |
| Time | O(n * c) | We iterate over n for each coin. c=4 here. |
| Space | O(n) | The dp array has size n+1 |
The algorithm scales linearly with `n` and handles the bounded coin efficiently using reverse iteration.
| Time | O(n) | Each coin processes the DP array once |
| Space | O(n) | DP array of size `n + 1` |
The algorithm processes only three unrestricted coins, so the total number of DP updates is proportional to `3n`, which simplifies to `O(n)`.
The memory usage is linear because we store one DP value for every sum from `0` through `n`.
## Test Cases
Provided examples
assert Solution().numberOfWays(4) == 4 # Four combinations assert Solution().numberOfWays(12) == 22 # Large n assert Solution().numberOfWays(5) == 4 # Small n
Boundary values
assert Solution().numberOfWays(1) == 1 # Only [1] assert Solution().numberOfWays(2) == 2 # [1,1],[2] assert Solution().numberOfWays(3) == 2 # [1,1,1],[1,2]
Stress cases
assert Solution().numberOfWays(100) >= 0 # Large n assert Solution().numberOfWays(105) >= 0 # Max n sol = Solution()
assert sol.numberOfWays(1) == 1 # only [1]
assert sol.numberOfWays(2) == 2 # [1,1], [2]
assert sol.numberOfWays(3) == 2 # [1,1,1], [1,2]
assert sol.numberOfWays(4) == 4 # includes single 4
assert sol.numberOfWays(5) == 4 # includes [1,4]
assert sol.numberOfWays(6) == 7 # combinations with 6 and 4
assert sol.numberOfWays(8) == 11 # includes two 4s
assert sol.numberOfWays(12) == 22 # provided example
assert sol.numberOfWays(9) > 0 # general positive case
assert sol.numberOfWays(100000) >= 0 # stress test for large input
| Test | Why |
| --- | --- |
| n=4 | Basic small input with bounded coin |
| n=12 | Medium input to validate correct DP |
| n=5 | Small input with multiple unbounded coins |
| n=1,2,3 | Minimum boundary cases |
| n=100,105 | Stress tests for performance |
## Edge Cases
The first edge case is the smallest input, `n=1`. The DP array handles this naturally since `dp[0]=1`, and the loop for coin=1 updates `dp[1]=1`. No overcount occurs.
The second edge case is when `n` is large enough to allow more than 2 fours, e.g., `n=12`. The reverse iteration ensures that we do not count `[4,4,4]` as a valid combination, enforcing the bounded limit correctly.
The third edge case is when `n
| `n = 1` | Smallest valid input |
| `n = 2` | Basic multiple-combination case |
| `n = 3` | No access to restricted coin |
| `n = 4` | First appearance of coin `4` |
| `n = 5` | Combination mixing `4` with smaller coins |
| `n = 6` | Includes unrestricted coin `6` |
| `n = 8` | Tests exactly two `4`s |
| `n = 12` | Verifies exclusion of three `4`s |
| `n = 9` | General medium-sized case |
| `n = 100000` | Large constraint stress test |
## Edge Cases
One important edge case occurs when `n < 4`. In these cases, the restricted `4` coin cannot be used at all. A careless implementation might attempt to access negative indices such as `dp[n - 4]`. The solution avoids this by explicitly checking `if n >= 4` before adding those terms.
Another important case is when `n` is between `4` and `7`. Here, exactly one `4` may be used, but two `4`s are impossible. The implementation handles this naturally with the `if n >= 8` condition before considering the two-`4` case.
A particularly subtle edge case is when larger sums could be formed using three or more `4`s. A naive unbounded coin DP including coin `4` would overcount invalid combinations. The solution completely avoids this issue by never allowing unrestricted transitions for coin `4`. Instead, it explicitly enumerates the only three valid possibilities: zero, one, or two uses.
Finally, very large values near `100000` are important for performance validation. Recursive solutions would time out or overflow the stack, while the linear DP solution remains efficient and comfortably fits within the problem constraints.