150NeetCode 150

72.Combination Sum

MediumBlind 75Backtracking ยท 2/9

Problem

Given an array of distinct positive integers candidates and an integer target, return all unique combinations of candidates whose numbers sum to target.

The same number may be chosen an unlimited number of times. Two combinations are the same if they use the same numbers with the same frequencies, regardless of order, so [2,2,3] and [3,2,2] count once.

Examples

Input:  candidates = [2, 3, 6, 7], target = 7
Output: [[2,2,3], [7]]
Explanation: 2 + 2 + 3 = 7 and 7 = 7. No other multiset works.
Input:  candidates = [2, 3, 5], target = 8
Output: [[2,2,2,2], [2,3,3], [3,5]]
Input:  candidates = [2], target = 1
Output: []

Constraints

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • All elements of candidates are distinct.
  • 1 <= target <= 40

๐Ÿ’ก Key Insight

To avoid duplicate orderings, only ever pick candidates at index >= start: once you move past a candidate you never come back to it. Because reuse is allowed, after picking candidates[i] you recurse with the same i. Sorting first lets you stop the loop as soon as a candidate exceeds the remaining target.

Brute Force (Include / Skip)

TimeO(2^(t/m)) roughly, where t = target and m = smallest candidateSpaceO(t/m)

Intuition

Treat it like Subsets with repetition: at index i either take candidates[i] again (stay at i) or move on to i + 1. Stop when the remaining sum is 0 (record it) or negative / out of candidates (dead end). Without sorting there is no early cut-off, so many dead branches are explored.

Algorithm

  1. dfs(i, remaining):
    • If remaining == 0, record a copy of path.
    • If remaining < 0 or i == n, return.
  2. Take: push candidates[i], dfs(i, remaining - candidates[i]), pop.
  3. Skip: dfs(i + 1, remaining).

Visualization

candidates = [2,3,6,7], target = 7

take 2 -> take 2 -> take 2 -> rem=1 -> take 2 -> rem=-1  dead
                              rem=1 -> skip to 3 -> rem=-2 dead ...
take 2 -> take 2 -> skip 2 -> take 3 -> rem=0   record [2,2,3]
skip 2 -> skip 3 -> skip 6 -> take 7 -> rem=0   record [7]

Code

java
import java.util.*;

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        dfs(candidates, 0, target, new ArrayList<>(), result);
        return result;
    }

    private void dfs(int[] cand, int i, int remaining, List<Integer> path, List<List<Integer>> result) {
        if (remaining == 0) {
            result.add(new ArrayList<>(path));
            return;
        }
        if (remaining < 0 || i == cand.length) {
            return; // overshot or ran out of candidates
        }
        path.add(cand[i]);                          // take cand[i] (may take it again)
        dfs(cand, i, remaining - cand[i], path, result);
        path.remove(path.size() - 1);
        dfs(cand, i + 1, remaining, path, result);  // move to the next candidate
    }
}

Complexity

  • Time: O(2^(t/m)) roughly, where t = target and m = smallest candidate โ€” the tree depth is at most t/m and each node branches twice.
  • Space: O(t/m) โ€” recursion depth.

Complexity Comparison

Approach Time Space
Include / Skip O(2^(t/m)) O(t/m)
Sorted Backtracking + Pruning O(n^(t/m)) worst, heavily pruned O(t/m)

Common Pitfalls

  • Recursing with i + 1 after picking โ€” that forbids reuse and misses [2,2,3].
  • Restarting the loop from 0 instead of start โ€” produces permutations like [2,3,2] and [3,2,2] as duplicates.
  • Using break without sorting first โ€” you would skip valid larger-index candidates that happen to be smaller.
  • Storing path directly in result instead of a copy.