39. Combination Sum
Date: 2026-03-17 Difficulty: Medium Topics: Backtracking, Recursion, Array Link: https://leetcode.com/problems/combination-sum/
Final Solution
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
result = []
def helper(curr: list[int], currSum: int, index: int):
if currSum == target:
result.append(curr.copy())
return
if currSum > target or index >= len(candidates):
return
helper(curr, currSum, index + 1)
curr.append(candidates[index])
currSum += candidates[index]
helper(curr, currSum, index)
curr.pop()
helper([], 0, 0)
return resultInitial Issues
- Forgot
returnafter appending valid result — kept exploring past the target - Missing
index >= len(candidates)bounds check — caused index out of bounds - Tried appending two elements before recursing without popping between — overcomplicated the include/exclude pattern
- Needed to return to simple subsets-style include/exclude structure
Key Learnings
Same include/exclude pattern as Subsets, with two twists:
- Include stays at same index (can reuse candidate)
- Exclude moves to next index (done with this candidate forever)
currSum > targetprunes branches early
Mental Model: “How many of each?”
Think of it like filling a shopping cart candidate by candidate:
- “How many 2s do I use?” → include keeps adding 2s, exclude moves to next
- “How many 3s do I use?” → same process
- Each unique path through the tree = a unique count of each candidate
Trace for candidates=[2,3], target=6
helper([], 0, index=0) → "How many 2s?"
exclude 2 (index=1) → "Zero 2s. How many 3s?"
include 3 → include 3 → [3,3]=6 ✓
include 2 (index=0) → "At least one 2. More?"
exclude 2 (index=1) → "Exactly one 2. How many 3s?"
include 3 → [2,3,3]=8 → too big
include 2 (index=0) → "At least two 2s. More?"
exclude 2 (index=1) → "Exactly two 2s. How many 3s?"
include 3 → [2,2,3]=7 → too big
include 2 → [2,2,2]=6 ✓
Result: [[3,3], [2,2,2]]
Nuances to Remember
- Exclude = “I’m done deciding how many of this candidate, move to next”
- Include = “Use at least one more of this candidate”
currSum > targetis critical — without it, include branch loops forever- Time: O(n^(T/M)) where T=target, M=min candidate — branching factor at each level
- Same backtracking structure as subsets: append → recurse → pop