Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix recursion issues in algorithms/sets/combination-sum #458

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
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).
cascandaliato authored Jan 30, 2020

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 3b9a3580d8dca2e1ba1c654e96edf48c74500b50
100 changes: 43 additions & 57 deletions src/algorithms/sets/combination-sum/combinationSum.js
Original file line number Diff line number Diff line change
@@ -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;
}