Interview Question Series | Season 2

The problem you’re tackling is the Combination Sum problem, where you’re given a list of unique integers (candidates) and a target number. You need to find all possible combinations of elements from candidates that sum up to the target. The key points are that numbers can be reused multiple times and each combination must be unique.

Backtracking Approach:

In this approach, we explore each possible combination by recursively trying to add candidates. For each candidate, we have the option of either including it in the current combination or skipping it.

Steps:

  1. Base Case: When the remaining target becomes 0, we’ve found a valid combination, so we add it to the result list.
  2. Recursive Case: For each candidate, we:
    • Skip it if it exceeds the remaining target.
    • Include it in the current combination and recursively try to find a solution with the reduced target.
    • After recursion, we backtrack by removing the last element added to the combination, allowing us to try the next candidate.

Key Points:

  • The combination must sum exactly to the target.
  • A candidate can be reused multiple times, but the combination must be unique.
  • Backtracking helps efficiently explore all possible combinations.

Code Explanation:

def combinationSum(candidates, target, current=None, start=0, result=None):
    # Initialize the result list and current combination list on first call
    if result is None:
        result = []
    if current is None:
        current = []
    
    # If the target is zero, we've found a valid combination
    if target == 0:
        result.append(list(current))  # Add the current combination to the result
        return

    # Try every candidate starting from the index `start`
    for i in range(start, len(candidates)):
        if candidates[i] > target:  # Skip if the candidate exceeds the remaining target
            continue
        
        # Include the current candidate in the combination
        current.append(candidates[i])
        # Recur with the reduced target, keeping the same start index to allow repetition of elements
        combinationSum(candidates, target - candidates[i], current, i, result)
        # Backtrack: Remove the last element added to explore other combinations
        current.pop()

    return result

Explanation:

  • Initialization:

    • result stores the list of all valid combinations.
    • current is the current combination being built.
    • start helps in maintaining the index to avoid duplicate combinations.
  • Base Case: If target == 0, it means the current combination sums up to the target, so it’s added to the result.

  • Recursive Case:

    • We iterate over the candidates starting from the start index to ensure combinations are formed by reusing candidates (since the index doesn’t change).
    • For each candidate, we reduce the target and recursively call combinationSum with the updated target.
    • After recursion, we backtrack by popping the last element in current to explore other possibilities.

Example Usage:

candidates = [2, 3, 6, 7]
target = 7
print(combinationSum(candidates, target))

Output:

[[2, 2, 3], [7]]

Explanation of Example:

  • The valid combinations that sum up to 7 are:
    • [2, 2, 3] (sum is 7)
    • [7] (sum is 7)

Time Complexity:

The time complexity of this approach can be approximated as O(2^n) where n is the number of candidates, since in the worst case, each candidate can either be included or excluded in every recursive call.

Space Complexity:

The space complexity is O(target) due to the depth of recursion and the storage needed for each combination being explored.

This backtracking approach is an efficient and clear way to solve the Combination Sum problem while ensuring all combinations are unique and sum up to the target.

Scroll to Top