Combination Sum — Solution in Python, Java & C++
Combination Sum solution explained: step-by-step approach, complexity and code in Python, Java, C++ and JavaScript. A medium problem.
Combination Sum is a medium coding problem on arrays and backtracking, asked in Airbnb, Snapchat, Zomato and Yahoo interviews.
- Difficulty: Medium
- Topics: Arrays, Backtracking
- Asked at: Airbnb, Snapchat, Zomato, Yahoo
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
Given an array of distinct positive integers candidates and a target, return all unique combinations where the chosen numbers sum to target. A number may be used unlimited times.
Output order: each combination in non-decreasing order; combinations listed in lexicographic order.
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: []
Constraints
1 <= candidates.length <= 62 <= candidates[i] <= 12 (distinct)1 <= target <= 15
Hints for Combination Sum
Hint 1
Sort candidates; DFS with a 'start' index so combinations stay non-decreasing.
Hint 2
Reusing a number means recursing with the SAME start index; break once a candidate exceeds the remainder.
How to solve Combination Sum
Two rules do all the work. Passing a start index forbids going back to a smaller candidate, which keeps every combination non-decreasing and stops the same multiset being found in several orders. Recursing with the same index rather than the next one is what allows a number to be reused without limit.
Approach
- Sort
candidatesascending — required both for the non-decreasing output and for the early break below. - Recurse with a
startindex and the remaining amountremain. - When
remainhits exactly0, the current list is a valid combination — record a copy. - Otherwise loop
ifromstart: ifcandidates[i] > remain, break — everything further right is larger still, so no candidate can fit. - Append
candidates[i]and recurse withstart = i(noti + 1), so the same value can be picked again, then remove it.
Why it works
Every multiset that sums to the target has exactly one non-decreasing arrangement, and the start rule means the search builds only that arrangement — so each answer is found once and duplicates are impossible without any de-duplication step. remain reaching 0 is the exact success condition, and since all candidates are positive, remain strictly decreases, guaranteeing the recursion terminates.
Complexity
- Time —
O(n^(target / min candidate)) - Space —
O(target / min candidate)
Pitfalls
- Recurse with
i, noti + 1. Usingi + 1bans reuse and silently solves a different problem. - The sorted
breakis what makes this fast;continuewould still be correct but explores branches that cannot possibly work. - Test
remain == 0for success and let thebreakhandle overshoot — aremain < 0check without sorting still works, but with sorting it is unreachable. - A target that cannot be formed must return an empty list, not a list holding an empty combination.
Reference solution
Python
from typing import List
def combinationSum(candidates: List[int], target: int) -> List[List[int]]:
s = sorted(candidates)
out = []
def go(start, remain, cur):
if remain == 0:
out.append(cur[:])
return
for i in range(start, len(s)):
if s[i] > remain:
break
cur.append(s[i])
go(i, remain - s[i], cur)
cur.pop()
go(0, target, [])
return outJava
public static int[][] combinationSum(int[] candidates, int target) {
int[] s = candidates.clone();
Arrays.sort(s);
List<int[]> out = new ArrayList<>();
List<Integer> cur = new ArrayList<>();
goCombSum(out, s, 0, target, cur);
return out.toArray(new int[0][]);
}
private static void goCombSum(List<int[]> out, int[] s, int start, int remain, List<Integer> cur) {
if (remain == 0) {
int[] snap = new int[cur.size()];
for (int i = 0; i < cur.size(); i++) snap[i] = cur.get(i);
out.add(snap);
return;
}
for (int i = start; i < s.length; i++) {
if (s[i] > remain) break;
cur.add(s[i]);
goCombSum(out, s, i, remain - s[i], cur);
cur.remove(cur.size() - 1);
}
}C++
void goCombSum(vector<vector<int>>& out, vector<int>& s, int start, int remain, vector<int>& cur) {
if (remain == 0) {
out.push_back(cur);
return;
}
for (int i = start; i < (int) s.size(); i++) {
if (s[i] > remain) break;
cur.push_back(s[i]);
goCombSum(out, s, i, remain - s[i], cur);
cur.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<int> s = candidates;
sort(s.begin(), s.end());
vector<vector<int>> out;
vector<int> cur;
goCombSum(out, s, 0, target, cur);
return out;
}JavaScript
var combinationSum = function(candidates, target) {
const s = candidates.slice().sort(function(a, b) { return a - b; });
const out = [];
function go(start, remain, cur) {
if (remain === 0) {
out.push(cur.slice());
return;
}
for (let i = start; i < s.length; i++) {
if (s[i] > remain) break;
cur.push(s[i]);
go(i, remain - s[i], cur);
cur.pop();
}
}
go(0, target, []);
return out;
};Also on the editorial tab: C, C#, Go, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.
All 1199 arrays problems · the whole catalogue
Learn the technique: Arrays · Backtracking