{x}
blog image

Combination Sum

Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target. You may return the combinations in any order.

The same number may be chosen from candidates an unlimited number of times. Two combinations are unique if the frequency of at least one of the chosen numbers is different.

The test cases are generated such that the number of unique combinations that sum up to target is less than 150 combinations for the given input.

 

Example 1:

Input: candidates = [2,3,6,7], target = 7
Output: [[2,2,3],[7]]
Explanation:
2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times.
7 is a candidate, and 7 = 7.
These are the only two combinations.

Example 2:

Input: candidates = [2,3,5], target = 8
Output: [[2,2,2,2],[2,3,3],[3,5]]

Example 3:

Input: candidates = [2], target = 1
Output: []

 

Constraints:

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

39. Combination Sum

Problem Description

Given a set of distinct positive integers candidates and a target integer target, find all unique combinations in candidates where the chosen numbers sum to target. Each number in candidates may be chosen an unlimited number of times.

Approach: Backtracking with Pruning

The most efficient approach is backtracking, enhanced with pruning to avoid unnecessary computations.

  1. Sorting: Sort the candidates array. This allows for efficient pruning because if the current sum exceeds the target, we can stop exploring further combinations involving larger numbers.

  2. Backtracking Function: The core logic lies in a recursive backtracking function. This function takes:

    • index: The current index in the candidates array.
    • remainingSum: The remaining sum needed to reach the target.
    • combination: The current combination being built.
    • result: A list to store all valid combinations.
  3. Base Cases:

    • If remainingSum is 0, a valid combination is found, so add combination to result.
    • If remainingSum is negative or index is out of bounds, this branch is invalid, so return.
  4. Recursive Step: For each element at the current index, explore two possibilities:

    • Include: Add the element to combination, recursively call the backtracking function with the updated remainingSum and the same index (allowing the same element to be chosen multiple times), and then remove the element from combination (backtracking).
    • Exclude: Recursively call the backtracking function with the same remainingSum and the next index (moving to the next element).
  5. Initial Call: The backtracking function is initially called with index = 0, remainingSum = target, an empty combination, and an empty result list.

Time and Space Complexity

  • Time Complexity: O(N * 2T), where N is the length of candidates and T is the target. In the worst case, each number might be included or excluded in every combination. However, the sorting and pruning significantly reduce the actual runtime in many cases.

  • Space Complexity: O(T) in the worst case, due to the maximum depth of the recursion stack and the space required to store the largest possible combination.

Code Implementation (Python)

def combinationSum(candidates, target):
    result = []
    candidates.sort()  # Sort for efficient pruning
 
    def backtrack(index, remainingSum, combination):
        if remainingSum == 0:
            result.append(combination.copy())  # Found a valid combination
            return
        if remainingSum < 0 or index >= len(candidates):
            return  # Invalid branch
 
        # Include the current element
        combination.append(candidates[index])
        backtrack(index, remainingSum - candidates[index], combination)
        combination.pop()  # Backtrack
 
        # Exclude the current element
        backtrack(index + 1, remainingSum, combination)
 
    backtrack(0, target, [])
    return result
 

The implementations in other languages (Java, C++, Go, TypeScript, Rust, C#, PHP) will follow a very similar structure, differing only in syntax and data structure specifics. The core backtracking algorithm remains the same.