Skip to content

Commit 3b9a358

Browse files
authoredJan 30, 2020
Refactor combinationSum to an iterative algo
- Refactor combinationSum to use an iterative algorithm (to avoid stack overflows). - Ignore candidates equal to zero (to avoid loops).
1 parent ba2d8dc commit 3b9a358

File tree

1 file changed

+43
-57
lines changed

1 file changed

+43
-57
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,51 @@
11
/**
2-
* @param {number[]} candidates - candidate numbers we're picking from.
3-
* @param {number} remainingSum - remaining sum after adding candidates to currentCombination.
4-
* @param {number[][]} finalCombinations - resulting list of combinations.
5-
* @param {number[]} currentCombination - currently explored candidates.
6-
* @param {number} startFrom - index of the candidate to start further exploration from.
7-
* @return {number[][]}
8-
*/
9-
function combinationSumRecursive(
10-
candidates,
11-
remainingSum,
12-
finalCombinations = [],
13-
currentCombination = [],
14-
startFrom = 0,
15-
) {
16-
if (remainingSum < 0) {
17-
// By adding another candidate we've gone below zero.
18-
// This would mean that the last candidate was not acceptable.
19-
return finalCombinations;
20-
}
21-
22-
if (remainingSum === 0) {
23-
// If after adding the previous candidate our remaining sum
24-
// became zero - we need to save the current combination since it is one
25-
// of the answers we're looking for.
26-
finalCombinations.push(currentCombination.slice());
27-
28-
return finalCombinations;
29-
}
30-
31-
// If we haven't reached zero yet let's continue to add all
32-
// possible candidates that are left.
33-
for (let candidateIndex = startFrom; candidateIndex < candidates.length; candidateIndex += 1) {
34-
const currentCandidate = candidates[candidateIndex];
35-
36-
// Let's try to add another candidate.
37-
currentCombination.push(currentCandidate);
38-
39-
// Explore further option with current candidate being added.
40-
combinationSumRecursive(
41-
candidates,
42-
remainingSum - currentCandidate,
43-
finalCombinations,
44-
currentCombination,
45-
candidateIndex,
46-
);
47-
48-
// BACKTRACKING.
49-
// Let's get back, exclude current candidate and try another ones later.
50-
currentCombination.pop();
51-
}
52-
53-
return finalCombinations;
54-
}
55-
56-
/**
57-
* Backtracking algorithm of finding all possible combination for specific sum.
2+
* Iterative algorithm to find all combinations (repetitions allowed)
3+
* that sum up to a given number (target) using elements
4+
* from a set of positive integers (candidates).
585
*
596
* @param {number[]} candidates
607
* @param {number} target
618
* @return {number[][]}
629
*/
6310
export default function combinationSum(candidates, target) {
64-
return combinationSumRecursive(candidates, target);
11+
const combinations = [];
12+
13+
const nonZeroCandidates = Array.from(new Set(candidates.filter(c => c > 0).slice().reverse()));
14+
const stack = nonZeroCandidates
15+
.map((candidate, index) => ({ candidateIndex: index, sum: candidate, prev: null }));
16+
17+
while (stack.length > 0) {
18+
const node = stack.pop();
19+
20+
if (node.sum === target) {
21+
/*
22+
If the cumulative sum matches the target value
23+
then we build the corresponding candidates combination
24+
by traversing the current branch back to its root...
25+
*/
26+
const combination = [];
27+
let currentNode = node;
28+
while (currentNode !== null) {
29+
const candidate = nonZeroCandidates[currentNode.candidateIndex];
30+
combination.push(candidate);
31+
currentNode = currentNode.prev;
32+
}
33+
combinations.push(combination);
34+
} else if (node.sum < target) {
35+
/*
36+
...otherwise we combine the current branch
37+
with any other candidate (as long as it is
38+
less or equal than the current candidate)
39+
and evaluate the new branches.
40+
*/
41+
for (let i = node.candidateIndex; i < nonZeroCandidates.length; i += 1) {
42+
stack.push({
43+
candidateIndex: i,
44+
sum: node.sum + nonZeroCandidates[i],
45+
prev: node,
46+
});
47+
}
48+
}
49+
}
50+
return combinations;
6551
}

0 commit comments

Comments
 (0)
Please sign in to comment.