The problem. Given an array of distinct integers candidates and a target, return all unique combinations of candidates that add up to target. The same number may be used any number of times. Two combinations are the same if they use the same numbers the same number of times.
candidates = [2, 3, 6, 7], target = 7 -> [[2, 2, 3], [7]]
candidates = [2, 3, 5], target = 8 -> [[2, 2, 2, 2], [2, 3, 3], [3, 5]]
candidates = [2], target = 1 -> []Use the loop-from-start template with one change: after taking candidates[i], recurse with start = i (not i + 1) — the same number may be taken again. Never going back to an earlier index keeps each combination in one order, so [2, 2, 3] isn't also built as [2, 3, 2]. Track the remaining target: at 0 the path is an answer. Sort the candidates, and the moment one is bigger than what's left, break — every later candidate is bigger still.
The take-or-skip template, where "take" stays on the same index.
function combinationSum(candidates, target) { const result = []; const path = []; const decide = (i, left) => { if (left === 0) { result.push([...path]); return; } if (i === candidates.length || left < 0) return; path.push(candidates[i]); // take candidates[i] (and maybe again) decide(i, left - candidates[i]); path.pop(); decide(i + 1, left); // or move on to the next candidate }; decide(0, target); return result;}Time O(2^(t/m)) roughly, where t is the target and m the smallest candidate (the deepest path takes the smallest number t/m times); space O(t/m) for the path.
Each node shows the amount still needed; branches whose candidate is too big are cut off.
function combinationSum(candidates, target) { const sorted = [...candidates].sort((a, b) => a - b); const result = []; const path = []; const build = (start, left) => { if (left === 0) { // exactly the target: one answer result.push([...path]); return; } for (let i = start; i < sorted.length; i++) { if (sorted[i] > left) break; // too big, and so is everything after it path.push(sorted[i]); build(i, left - sorted[i]); // i, not i + 1: it may be used again path.pop(); } }; build(0, target); return result;}Same worst case, much smaller tree in practice: no call is ever made with a negative remainder, and the break skips every larger candidate at once. Space O(t/m).
[2], target 1: [].[1], target 5: [[1,1,1,1,1]].i builds [2,3,2] and [3,2,2] as well.