R
Rakesh Reddy Peddamallu
Guest
The Combination Sum problem is a classic backtracking question from LeetCode (#39).
We are given:
We must return all unique combinations where the chosen numbers add up exactly to
Numbers can be reused unlimited times.
Output:
Why?
This is a search problem. Think of it like exploring all possible "paths" of numbers until:
We use DFS + backtracking:
Letβs analyze:
In practice, it runs much faster due to pruning (
Continue reading...
We are given:
- An array of positive integers
candidates
(no duplicates inside). - A target integer
target
.
We must return all unique combinations where the chosen numbers add up exactly to
target
.Numbers can be reused unlimited times.
Example
Code:
candidates = [2, 3, 6, 7], target = 7
Output:
Code:
[
[2, 2, 3],
[7]
]
Why?
[2, 2, 3]
β adds to 7[7]
β also adds to 7- No other valid combinations exist.
Approach β Backtracking (DFS)
This is a search problem. Think of it like exploring all possible "paths" of numbers until:
- The sum equals
target
βvalid combination
- The sum exceeds
target
βinvalid, stop
- Weβve tried all numbers β
stop
We use DFS + backtracking:
- Start at index
0
with an empty path[]
and total0
. - At each step:
- Either include the current candidate (stay on same index since we can reuse it).
- Or skip it and move to the next index.
- Whenever
total === target
, we push a copy of the path into results. - Backtrack by popping out the last choice and continue exploring.
- Whenever
Code (JavaScript)
Code:
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum = function (candidates, target) {
let res = [];
const dfs = (i, curr, total) => {
if (total === target) {
res.push([...curr]); // store valid combination
return;
}
if (i >= candidates.length || total > target) {
return; // stop exploring
}
// 1. Choose the current number
curr.push(candidates[i]);
dfs(i, curr, total + candidates[i]); // stay on i since reuse is allowed
curr.pop(); // backtrack
// 2. Skip the current number
dfs(i + 1, curr, total);
};
dfs(0, [], 0);
return res;
};
Time and Space Complexity
Letβs analyze:
Time Complexity
- In the worst case, each number can be chosen multiple times until we exceed the target.
- The height of the recursion tree is at most
target / min(candidates)
. - At each level, we branch into choose or skip β about
2^n
style growth. - Worst-case complexity: O(2^n * t) where
t
is the target (because we keep adding numbers until reaching it).
In practice, it runs much faster due to pruning (
total > target
).Space Complexity
- O(target/min(candidates)) for the recursion depth (stack space).
- Plus storage for results, which could be exponential in the number of combinations.
Key Takeaways
- Backtracking is perfect when we need all possible solutions.
The trick is in branching:
- Reuse the current candidate β
dfs(i, ...)
- Skip it β
dfs(i+1, ...)
- Reuse the current candidate β
The stopping conditionif (i >= candidates.length || total > target)
prevents infinite recursion.
Continue reading...