AlgoMaster Logo

Combination Sum

Last Updated: March 29, 2026

medium
4 min read

Understanding the Problem

We're given an array of distinct positive integers and a target sum. We need to find every way to pick numbers from the array (with unlimited reuse) that add up exactly to the target. The order of numbers in a combination doesn't matter, so [2, 2, 3] and [2, 3, 2] are the same combination and should only appear once.

What makes this problem interesting is the "unlimited reuse" aspect. Unlike problems where each element can only be used once, here we can pick the same candidate as many times as we want. This means the search space is potentially large, but the constraints (target up to 40, candidates at least 2) keep things manageable.

The key observation is that this is a classic backtracking problem. We systematically explore all ways to build up to the target by trying each candidate and recursing with the remaining sum. To avoid duplicate combinations, we enforce an ordering: once we move past a candidate, we never go back to it.

Key Constraints:

  • 1 <= candidates.length <= 30 → Up to 30 candidates, which is small enough for backtracking. With good pruning, we won't hit time limits.
  • 2 <= candidates[i] <= 40 → Every candidate is at least 2. This means the maximum depth of recursion is target / min(candidates) = 40 / 2 = 20. That's shallow.
  • 1 <= target <= 40 → A small target keeps the total number of valid combinations manageable (the problem guarantees fewer than 150 combinations).
  • All elements are distinct → No need to worry about duplicate candidates producing duplicate combinations.

Approach 1: Brute Force (Generate All Combinations)

Intuition

The most direct way to think about this: for each candidate, decide how many times to include it (0, 1, 2, ... up to target / candidate), then move on to the next candidate. After we've made a decision for every candidate, check if the total sum equals the target.

This is essentially enumerating every possible "frequency vector." If we have candidates [2, 3, 7] and target 7, we're trying all combinations like (0 twos, 0 threes, 0 sevens), (1 two, 0 threes, 0 sevens), ..., (3 twos, 0 threes, 0 sevens), (0 twos, 1 three, 0 sevens), and so on. Most of these will overshoot the target or fall short, but we check every possibility.

This approach does no pruning at all. It generates all possible frequency assignments and only filters valid ones at the end.

Algorithm

  1. Start with the first candidate and an empty combination.
  2. For the current candidate, try using it 0 times, 1 time, 2 times, etc., up to the point where the sum would exceed the target.
  3. For each choice, move on to the next candidate and repeat.
  4. After processing all candidates, if the running sum equals the target, record the combination.
  5. Collect all valid combinations and return them.

Example Walkthrough

1Start: try candidate[0]=2 with count=0,1,2,3
0
2
index=0
1
3
2
6
3
7
1/7
1Building combination: start empty
1/7

Code

This approach works correctly but doesn't abandon hopeless branches early. What if instead of deciding the full count upfront, we added one candidate at a time and checked the remaining target after each addition?

Approach 2: Backtracking with Pruning (Optimal)

Intuition

Instead of deciding how many times to use each candidate upfront, backtracking takes a more incremental approach: pick one candidate, subtract it from the target, and recurse with the reduced target. If the remaining target hits zero, we've found a valid combination. If it goes negative, we went too far and backtrack.

The critical insight for avoiding duplicates is this: we maintain a "start index." When we pick a candidate at index i, the next recursive call can only consider candidates at index i or later. This means we never pick candidate[2] after candidate[3], which prevents generating [3, 2] as a separate combination from [2, 3].

Sorting the candidates first gives us an extra pruning advantage. If we're iterating through candidates and the current one exceeds the remaining target, every subsequent candidate will too (since they're sorted in ascending order). So we can break out of the loop immediately instead of checking each one.

Algorithm

  1. Sort the candidates array in ascending order.
  2. Start a recursive backtracking function with the full target and start index 0.
  3. If the remaining target is 0, we found a valid combination. Add it to the result.
  4. Iterate through candidates starting from the current start index.
  5. If the current candidate exceeds the remaining target, break (all further candidates are larger).
  6. Add the candidate to the current combination.
  7. Recurse with the reduced target and the same start index (since we can reuse the candidate).
  8. Remove the last candidate (backtrack) and try the next one.

Example Walkthrough

1Sorted candidates. Start: remaining=7, pick candidate[0]=2
0
2
pick
1
3
2
6
3
7
1/9
1Start empty, remaining = 7
1/9

Code