mirror of
https://github.com/trekhleb/javascript-algorithms.git
synced 2025-07-07 01:44:52 +08:00
Add "Combination Sum" backtracking algorithm.
This commit is contained in:
@ -70,6 +70,7 @@ a set of rules that precisely define a sequence of operations.
|
|||||||
* `A` [Shortest Common Supersequence](src/algorithms/sets/shortest-common-supersequence) (SCS)
|
* `A` [Shortest Common Supersequence](src/algorithms/sets/shortest-common-supersequence) (SCS)
|
||||||
* `A` [Knapsack Problem](src/algorithms/sets/knapsack-problem) - "0/1" and "Unbound" ones
|
* `A` [Knapsack Problem](src/algorithms/sets/knapsack-problem) - "0/1" and "Unbound" ones
|
||||||
* `A` [Maximum Subarray](src/algorithms/sets/maximum-subarray) - "Brute Force" and "Dynamic Programming" (Kadane's) versions
|
* `A` [Maximum Subarray](src/algorithms/sets/maximum-subarray) - "Brute Force" and "Dynamic Programming" (Kadane's) versions
|
||||||
|
* `A` [Combination Sum](src/algorithms/sets/combination-sum) - find all combinations that form specific sum
|
||||||
* **Strings**
|
* **Strings**
|
||||||
* `A` [Levenshtein Distance](src/algorithms/string/levenshtein-distance) - minimum edit distance between two sequences
|
* `A` [Levenshtein Distance](src/algorithms/string/levenshtein-distance) - minimum edit distance between two sequences
|
||||||
* `B` [Hamming Distance](src/algorithms/string/hamming-distance) - number of positions at which the symbols are different
|
* `B` [Hamming Distance](src/algorithms/string/hamming-distance) - number of positions at which the symbols are different
|
||||||
@ -156,6 +157,7 @@ different path of finding a solution. Normally the DFS traversal of state-space
|
|||||||
* `A` [Hamiltonian Cycle](src/algorithms/graph/hamiltonian-cycle) - Visit every vertex exactly once
|
* `A` [Hamiltonian Cycle](src/algorithms/graph/hamiltonian-cycle) - Visit every vertex exactly once
|
||||||
* `A` [N-Queens Problem](src/algorithms/uncategorized/n-queens)
|
* `A` [N-Queens Problem](src/algorithms/uncategorized/n-queens)
|
||||||
* `A` [Knight's Tour](src/algorithms/uncategorized/knight-tour)
|
* `A` [Knight's Tour](src/algorithms/uncategorized/knight-tour)
|
||||||
|
* `A` [Combination Sum](src/algorithms/sets/combination-sum) - find all combinations that form specific sum
|
||||||
* **Branch & Bound** - remember the lowest-cost solution found at each stage of the backtracking
|
* **Branch & Bound** - remember the lowest-cost solution found at each stage of the backtracking
|
||||||
search, and use the cost of the lowest-cost solution found so far as a lower bound on the cost of
|
search, and use the cost of the lowest-cost solution found so far as a lower bound on the cost of
|
||||||
a least-cost solution to the problem, in order to discard partial solutions with costs larger than the
|
a least-cost solution to the problem, in order to discard partial solutions with costs larger than the
|
||||||
|
60
src/algorithms/sets/combination-sum/README.md
Normal file
60
src/algorithms/sets/combination-sum/README.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Combination Sum Problem
|
||||||
|
|
||||||
|
Given a **set** of candidate numbers (`candidates`) **(without duplicates)** and
|
||||||
|
a target number (`target`), find all unique combinations in `candidates` where
|
||||||
|
the candidate numbers sums to `target`.
|
||||||
|
|
||||||
|
The **same** repeated number may be chosen from `candidates` unlimited number
|
||||||
|
of times.
|
||||||
|
|
||||||
|
**Note:**
|
||||||
|
|
||||||
|
- All numbers (including `target`) will be positive integers.
|
||||||
|
- The solution set must not contain duplicate combinations.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
Input: candidates = [2,3,6,7], target = 7,
|
||||||
|
|
||||||
|
A solution set is:
|
||||||
|
[
|
||||||
|
[7],
|
||||||
|
[2,2,3]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
Input: candidates = [2,3,5], target = 8,
|
||||||
|
|
||||||
|
A solution set is:
|
||||||
|
[
|
||||||
|
[2,2,2,2],
|
||||||
|
[2,3,3],
|
||||||
|
[3,5]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
Here is an example of decision tree for the situation when `candidates = [2, 3]` and `target = 6`:
|
||||||
|
|
||||||
|
```
|
||||||
|
0
|
||||||
|
/ \
|
||||||
|
+2 +3
|
||||||
|
/ \ \
|
||||||
|
+2 +3 +3
|
||||||
|
/ \ / \ \
|
||||||
|
+2 ✘ ✘ ✘ ✓
|
||||||
|
/ \
|
||||||
|
✓ ✘
|
||||||
|
```
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [LeetCode](https://leetcode.com/problems/combination-sum/description/)
|
@ -0,0 +1,24 @@
|
|||||||
|
import combinationSum from '../combinationSum';
|
||||||
|
|
||||||
|
describe('combinationSum', () => {
|
||||||
|
it('should find all combinations with specific sum', () => {
|
||||||
|
expect(combinationSum([1], 4)).toEqual([
|
||||||
|
[1, 1, 1, 1],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(combinationSum([2, 3, 6, 7], 7)).toEqual([
|
||||||
|
[2, 2, 3],
|
||||||
|
[7],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(combinationSum([2, 3, 5], 8)).toEqual([
|
||||||
|
[2, 2, 2, 2],
|
||||||
|
[2, 3, 3],
|
||||||
|
[3, 5],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(combinationSum([2, 5], 3)).toEqual([]);
|
||||||
|
|
||||||
|
expect(combinationSum([], 3)).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
65
src/algorithms/sets/combination-sum/combinationSum.js
Normal file
65
src/algorithms/sets/combination-sum/combinationSum.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* @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 last candidate was not acceptable.
|
||||||
|
return finalCombinations;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingSum === 0) {
|
||||||
|
// In case if after adding the previous candidate out remaining sum
|
||||||
|
// became zero we need to same current combination since it is one
|
||||||
|
// of the answer we're looking for.
|
||||||
|
finalCombinations.push(currentCombination.slice());
|
||||||
|
|
||||||
|
return finalCombinations;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case 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} target
|
||||||
|
* @return {number[][]}
|
||||||
|
*/
|
||||||
|
export default function combinationSum(candidates, target) {
|
||||||
|
return combinationSumRecursive(candidates, target);
|
||||||
|
}
|
Reference in New Issue
Block a user