mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-11-10 11:09:43 +08:00
Merge 2f60cab835
into ca3d16dcce
This commit is contained in:
commit
cc8d82a7c1
@ -38,21 +38,22 @@ A solution set is:
|
||||
## Explanations
|
||||
|
||||
Since the problem is to get all the possible results, not the best or the
|
||||
number of result, thus we don’t need to consider DP (dynamic programming),
|
||||
backtracking approach using recursion is needed to handle it.
|
||||
number of result, we don’t need to consider dynamic programming.
|
||||
We do instead a depth-first traversal of the decision tree,
|
||||
using an iterative implementation to avoid stack overflows.
|
||||
|
||||
Here is an example of decision tree for the situation when `candidates = [2, 3]` and `target = 6`:
|
||||
|
||||
```
|
||||
0
|
||||
/ \
|
||||
+2 +3
|
||||
/ \ \
|
||||
+2 +3 +3
|
||||
/ \ / \ \
|
||||
+2 ✘ ✘ ✘ ✓
|
||||
/ \
|
||||
✓ ✘
|
||||
0
|
||||
/ \
|
||||
+3 +2
|
||||
/ \ \
|
||||
+3 +2 +2
|
||||
/ \ \
|
||||
✓ +2 +2
|
||||
\ \
|
||||
✘ ✓
|
||||
```
|
||||
|
||||
## References
|
||||
|
@ -2,6 +2,16 @@ import combinationSum from '../combinationSum';
|
||||
|
||||
describe('combinationSum', () => {
|
||||
it('should find all combinations with specific sum', () => {
|
||||
expect(combinationSum([1], 100000)).toHaveLength(1);
|
||||
|
||||
expect(
|
||||
combinationSum([1], 100000)[0]
|
||||
.every(el => el === 1),
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
expect(combinationSum([0, 2], 6)).toEqual([[2, 2, 2]]);
|
||||
|
||||
expect(combinationSum([1], 4)).toEqual([
|
||||
[1, 1, 1, 1],
|
||||
]);
|
||||
|
@ -1,65 +1,51 @@
|
||||
/**
|
||||
* @param {number[]} candidates - candidate numbers we're picking from.
|
||||
* @param {number} remainingSum - remaining sum after adding candidates to currentCombination.
|
||||
* @param {number[][]} finalCombinations - resulting list of combinations.
|
||||
* @param {number[]} currentCombination - currently explored candidates.
|
||||
* @param {number} startFrom - index of the candidate to start further exploration from.
|
||||
* @return {number[][]}
|
||||
*/
|
||||
function combinationSumRecursive(
|
||||
candidates,
|
||||
remainingSum,
|
||||
finalCombinations = [],
|
||||
currentCombination = [],
|
||||
startFrom = 0,
|
||||
) {
|
||||
if (remainingSum < 0) {
|
||||
// By adding another candidate we've gone below zero.
|
||||
// This would mean that the last candidate was not acceptable.
|
||||
return finalCombinations;
|
||||
}
|
||||
|
||||
if (remainingSum === 0) {
|
||||
// If after adding the previous candidate our remaining sum
|
||||
// became zero - we need to save the current combination since it is one
|
||||
// of the answers we're looking for.
|
||||
finalCombinations.push(currentCombination.slice());
|
||||
|
||||
return finalCombinations;
|
||||
}
|
||||
|
||||
// If we haven't reached zero yet let's continue to add all
|
||||
// possible candidates that are left.
|
||||
for (let candidateIndex = startFrom; candidateIndex < candidates.length; candidateIndex += 1) {
|
||||
const currentCandidate = candidates[candidateIndex];
|
||||
|
||||
// Let's try to add another candidate.
|
||||
currentCombination.push(currentCandidate);
|
||||
|
||||
// Explore further option with current candidate being added.
|
||||
combinationSumRecursive(
|
||||
candidates,
|
||||
remainingSum - currentCandidate,
|
||||
finalCombinations,
|
||||
currentCombination,
|
||||
candidateIndex,
|
||||
);
|
||||
|
||||
// BACKTRACKING.
|
||||
// Let's get back, exclude current candidate and try another ones later.
|
||||
currentCombination.pop();
|
||||
}
|
||||
|
||||
return finalCombinations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backtracking algorithm of finding all possible combination for specific sum.
|
||||
* Iterative algorithm to find all combinations (repetitions allowed)
|
||||
* that sum up to a given number (target) using elements
|
||||
* from a set of positive integers (candidates).
|
||||
*
|
||||
* @param {number[]} candidates
|
||||
* @param {number} target
|
||||
* @return {number[][]}
|
||||
*/
|
||||
export default function combinationSum(candidates, target) {
|
||||
return combinationSumRecursive(candidates, target);
|
||||
const combinations = [];
|
||||
|
||||
const nonZeroCandidates = Array.from(new Set(candidates.filter(c => c > 0).slice().reverse()));
|
||||
const stack = nonZeroCandidates
|
||||
.map((candidate, index) => ({ candidateIndex: index, sum: candidate, prev: null }));
|
||||
|
||||
while (stack.length > 0) {
|
||||
const node = stack.pop();
|
||||
|
||||
if (node.sum === target) {
|
||||
/*
|
||||
If the cumulative sum matches the target value
|
||||
then we build the corresponding candidates combination
|
||||
by traversing the current branch back to its root...
|
||||
*/
|
||||
const combination = [];
|
||||
let currentNode = node;
|
||||
while (currentNode !== null) {
|
||||
const candidate = nonZeroCandidates[currentNode.candidateIndex];
|
||||
combination.push(candidate);
|
||||
currentNode = currentNode.prev;
|
||||
}
|
||||
combinations.push(combination);
|
||||
} else if (node.sum < target) {
|
||||
/*
|
||||
...otherwise we combine the current branch
|
||||
with any other candidate (as long as it is
|
||||
less or equal than the current candidate)
|
||||
and evaluate the new branches.
|
||||
*/
|
||||
for (let i = node.candidateIndex; i < nonZeroCandidates.length; i += 1) {
|
||||
stack.push({
|
||||
candidateIndex: i,
|
||||
sum: node.sum + nonZeroCandidates[i],
|
||||
prev: node,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return combinations;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user