|
1 | 1 | /**
|
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). |
58 | 5 | *
|
59 | 6 | * @param {number[]} candidates
|
60 | 7 | * @param {number} target
|
61 | 8 | * @return {number[][]}
|
62 | 9 | */
|
63 | 10 | 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; |
65 | 51 | }
|
0 commit comments