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
|
## Explanations
|
||||||
|
|
||||||
Since the problem is to get all the possible results, not the best or the
|
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),
|
number of result, we don’t need to consider dynamic programming.
|
||||||
backtracking approach using recursion is needed to handle it.
|
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`:
|
Here is an example of decision tree for the situation when `candidates = [2, 3]` and `target = 6`:
|
||||||
|
|
||||||
```
|
```
|
||||||
0
|
0
|
||||||
/ \
|
/ \
|
||||||
+2 +3
|
+3 +2
|
||||||
/ \ \
|
/ \ \
|
||||||
+2 +3 +3
|
+3 +2 +2
|
||||||
/ \ / \ \
|
/ \ \
|
||||||
+2 ✘ ✘ ✘ ✓
|
✓ +2 +2
|
||||||
/ \
|
\ \
|
||||||
✓ ✘
|
✘ ✓
|
||||||
```
|
```
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
@ -2,6 +2,16 @@ import combinationSum from '../combinationSum';
|
|||||||
|
|
||||||
describe('combinationSum', () => {
|
describe('combinationSum', () => {
|
||||||
it('should find all combinations with specific sum', () => {
|
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([
|
expect(combinationSum([1], 4)).toEqual([
|
||||||
[1, 1, 1, 1],
|
[1, 1, 1, 1],
|
||||||
]);
|
]);
|
||||||
|
@ -1,65 +1,51 @@
|
|||||||
/**
|
/**
|
||||||
* @param {number[]} candidates - candidate numbers we're picking from.
|
* Iterative algorithm to find all combinations (repetitions allowed)
|
||||||
* @param {number} remainingSum - remaining sum after adding candidates to currentCombination.
|
* that sum up to a given number (target) using elements
|
||||||
* @param {number[][]} finalCombinations - resulting list of combinations.
|
* from a set of positive integers (candidates).
|
||||||
* @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.
|
|
||||||
*
|
*
|
||||||
* @param {number[]} candidates
|
* @param {number[]} candidates
|
||||||
* @param {number} target
|
* @param {number} target
|
||||||
* @return {number[][]}
|
* @return {number[][]}
|
||||||
*/
|
*/
|
||||||
export default function combinationSum(candidates, target) {
|
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