Framework Thinking - Neetcode 150 - Combination Sum
Here is solutions for Combination Sum.
1. Understand the problem
-
You are given:
-
An array of distinct positive integers candidates
-
A target integer target
-
-
Your task is to return all unique combinations of candidates where the chosen numbers sum to target.
2. Clarify constraints, asks 4 - 5 questions including edge cases.
- Are all numbers positive?
- Yes (important to avoid infinite loops)
- Can candidates contain duplicates?
- No (distinct integers)
- Can I reuse the same number multiple times?
- Yes (unlimited usage)
- What should I return if no combination exists?
- Return an empty list []
- What about edge cases like an empty candidates list or target = 0?
-
candidates = [] → return []
-
target = 0 → return [[]] (one valid “empty” combination)
3. Explore examples.
- Example 1
Input: candidates = [2,3,6,7], target = 7
Output: [[2,2,3],[7]]
- 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: []
4. Brainstorm 2 - 3 solutions, naive solution first and optimize later. Explain the key idea of each solution.
4.1. Solution 1: Naive Backtracking (DFS) - Time O(k^N), Space O(k)
-
At each step:
-
Choose a number
-
Subtract it from target
-
Recurse again with the same index (allow reuse)
-
-
Stop when:
-
target == 0 → valid combination
-
target < 0 → invalid path
-
-
Time: O(k^N)
-
Space: O(k)
4.2. Solution 2: Optimized Backtracking with Sorting & Pruning when sum > remaining - Time O(k^N), Space O(k)
-
Optimization:
-
If candidates[i] > remaining, break the loop
-
Avoid unnecessary recursion
-
-
Time: O(k^N)
-
Space: O(k)
4.3. Solution 3: Dynamic Programming (Advanced)
-
Idea:
-
Use DP where dp[t] contains all combinations that sum to t.
-
This is more complex to implement and memory-heavy, so backtracking is preferred in interviews.
-
-
Time: O(T)
-
Space: O(T)
5. Implement solutions.
5.1. Backtracking - Time O(2^K), Space O(K)
class Solution:
def combinationSum(self, nums: List[int], target: int) -> List[List[int]]:
res = []
def dfs(i, cur, total):
if total == target:
res.append(cur.copy())
return
if i >= len(nums) or total > target:
return
# Pick nums[i]
cur.append(nums[i])
dfs(i, cur, total + nums[i])
cur.pop()
# Skip nums[i]
dfs(i + 1, cur, total)
dfs(0, [], 0)
return res
-
Time: O(2^K)
-
Space: O(K)
-
Idea:
Start at i=0 (2)
├── pick 2 → [2]
│ ├── pick 2 → [2,2]
│ │ ├── pick 2 → [2,2,2] ✅
│ │ └── skip 2 → try 3
│ └── skip 2 → try 3
└── skip 2 → try 3
5.2. Backtracking with Prunning - Time O(2^k), Space O(k), k < K by prunning
class Solution:
def combinationSum(self, nums: List[int], target: int) -> List[List[int]]:
res = []
nums.sort()
def dfs(i, cur, total):
if total == target:
res.append(cur.copy())
return
for j in range(i, len(nums)):
if total + nums[j] > target:
return
cur.append(nums[j])
dfs(j, cur, total + nums[j])
cur.pop()
dfs(0, [], 0)
return res

-
Time: O(2^k)
-
Space: O(k)
Note: (2,2), (2,3), (2,6), (2,7) => (2,3,3), (2,3,6), (2,3,7), prevent to duplicate value (2,3,2) and (2,2,3)
6. Dry run testcases.
