From 3b9a3580d8dca2e1ba1c654e96edf48c74500b50 Mon Sep 17 00:00:00 2001
From: casca <8927157+casca@users.noreply.github.com>
Date: Thu, 30 Jan 2020 21:31:04 +0100
Subject: [PATCH 1/3] 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).
---
 .../sets/combination-sum/combinationSum.js    | 100 ++++++++----------
 1 file changed, 43 insertions(+), 57 deletions(-)

diff --git a/src/algorithms/sets/combination-sum/combinationSum.js b/src/algorithms/sets/combination-sum/combinationSum.js
index dd396a8cb4..d37c521c76 100644
--- a/src/algorithms/sets/combination-sum/combinationSum.js
+++ b/src/algorithms/sets/combination-sum/combinationSum.js
@@ -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;
 }

From f6408ffb57a13a5f5acdc8dc121f38fda1b6341b Mon Sep 17 00:00:00 2001
From: casca <8927157+casca@users.noreply.github.com>
Date: Thu, 30 Jan 2020 21:32:49 +0100
Subject: [PATCH 2/3] Add test cases mentioned in #308

---
 .../combination-sum/__test__/combinationSum.test.js    | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/algorithms/sets/combination-sum/__test__/combinationSum.test.js b/src/algorithms/sets/combination-sum/__test__/combinationSum.test.js
index 7b196bf2a2..8cb1c4adc9 100644
--- a/src/algorithms/sets/combination-sum/__test__/combinationSum.test.js
+++ b/src/algorithms/sets/combination-sum/__test__/combinationSum.test.js
@@ -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],
     ]);

From 2f60cab8351dced1c72e016b31cf20ad38b0b0cb Mon Sep 17 00:00:00 2001
From: casca <8927157+casca@users.noreply.github.com>
Date: Thu, 30 Jan 2020 21:33:47 +0100
Subject: [PATCH 3/3] Update combinationSum README to reflect new
 implementation

---
 src/algorithms/sets/combination-sum/README.md | 23 ++++++++++---------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/src/algorithms/sets/combination-sum/README.md b/src/algorithms/sets/combination-sum/README.md
index cb14f1bae8..379e059078 100644
--- a/src/algorithms/sets/combination-sum/README.md
+++ b/src/algorithms/sets/combination-sum/README.md
@@ -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