From 4e303215135d35940f410afc1695b294646d17ce Mon Sep 17 00:00:00 2001
From: Tejasrahane <161036451+Tejasrahane@users.noreply.github.com>
Date: Tue, 21 Oct 2025 06:57:31 +0530
Subject: [PATCH 1/4] Add: CombinationSum algorithm (Fixes #6804)
Implements the Combination Sum algorithm using recursion and backtracking. This class finds all unique combinations of distinct integers that sum to a target value, where numbers can be used multiple times.
- Added comprehensive JavaDoc documentation
- Included Wikipedia and LeetCode references
- Follows project naming conventions
- Uses proper recursion and backtracking techniques
Fixes #6804
---
.../recursion/CombinationSum.java | 60 +++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/recursion/CombinationSum.java
diff --git a/src/main/java/com/thealgorithms/recursion/CombinationSum.java b/src/main/java/com/thealgorithms/recursion/CombinationSum.java
new file mode 100644
index 000000000000..caeb3cf70d25
--- /dev/null
+++ b/src/main/java/com/thealgorithms/recursion/CombinationSum.java
@@ -0,0 +1,60 @@
+package com.thealgorithms.recursion;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class implements the Combination Sum algorithm using recursion and backtracking.
+ * Given an array of distinct integers candidates and a target integer target,
+ * return a list of all unique combinations of candidates where the chosen numbers sum to target.
+ * The same number may be chosen from candidates an unlimited number of times.
+ *
+ * @see Subset Sum Problem (Wikipedia)
+ * @see Combination Sum (LeetCode)
+ * @author Tejas Rahane
+ */
+public final class CombinationSum {
+ private CombinationSum() {
+ }
+
+ /**
+ * Finds all unique combinations that sum to target.
+ *
+ * @param candidates Array of distinct integers
+ * @param target Target sum
+ * @return List of all unique combinations that sum to target
+ */
+ public static List> combinationSum(int[] candidates, int target) {
+ List> result = new ArrayList<>();
+ if (candidates == null || candidates.length == 0) {
+ return result;
+ }
+ backtrack(candidates, target, 0, new ArrayList<>(), result);
+ return result;
+ }
+
+ /**
+ * Backtracking helper method to find all combinations.
+ *
+ * @param candidates Array of distinct integers
+ * @param target Remaining target sum
+ * @param start Starting index for candidates
+ * @param current Current combination being built
+ * @param result List to store all valid combinations
+ */
+ private static void backtrack(int[] candidates, int target, int start, List current, List> result) {
+ if (target == 0) {
+ result.add(new ArrayList<>(current));
+ return;
+ }
+ if (target < 0) {
+ return;
+ }
+
+ for (int i = start; i < candidates.length; i++) {
+ current.add(candidates[i]);
+ backtrack(candidates, target - candidates[i], i, current, result);
+ current.remove(current.size() - 1);
+ }
+ }
+}
From 80a3aa803ab6a4dc64e11d21089cbde4081025d2 Mon Sep 17 00:00:00 2001
From: Tejasrahane <161036451+Tejasrahane@users.noreply.github.com>
Date: Tue, 21 Oct 2025 19:16:51 +0530
Subject: [PATCH 2/4] Add tests for CombinationSum algorithm
This test class covers various scenarios for the CombinationSum algorithm, including edge cases and multiple combinations.
---
.../recursion/CombinationSumTest.java | 145 ++++++++++++++++++
1 file changed, 145 insertions(+)
create mode 100644 src/test/java/com/thealgorithms/recursion/CombinationSumTest.java
diff --git a/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java b/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java
new file mode 100644
index 000000000000..f5d3d4fbccc3
--- /dev/null
+++ b/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java
@@ -0,0 +1,145 @@
+package com.thealgorithms.recursion;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Comprehensive test class for CombinationSum algorithm
+ * Tests various scenarios including edge cases
+ */
+class CombinationSumTest {
+
+ @Test
+ void testBasicCase() {
+ CombinationSum cs = new CombinationSum();
+ int[] candidates = {2, 3, 6, 7};
+ int target = 7;
+ List> result = cs.combinationSum(candidates, target);
+
+ assertTrue(result.contains(Arrays.asList(2, 2, 3)));
+ assertTrue(result.contains(Arrays.asList(7)));
+ assertEquals(2, result.size());
+ }
+
+ @Test
+ void testMultipleCombinations() {
+ CombinationSum cs = new CombinationSum();
+ int[] candidates = {2, 3, 5};
+ int target = 8;
+ List> result = cs.combinationSum(candidates, target);
+
+ assertTrue(result.contains(Arrays.asList(2, 2, 2, 2)));
+ assertTrue(result.contains(Arrays.asList(2, 3, 3)));
+ assertTrue(result.contains(Arrays.asList(3, 5)));
+ assertEquals(3, result.size());
+ }
+
+ @Test
+ void testNoSolution() {
+ CombinationSum cs = new CombinationSum();
+ int[] candidates = {2};
+ int target = 1;
+ List> result = cs.combinationSum(candidates, target);
+
+ assertTrue(result.isEmpty());
+ }
+
+ @Test
+ void testSingleElement() {
+ CombinationSum cs = new CombinationSum();
+ int[] candidates = {1};
+ int target = 1;
+ List> result = cs.combinationSum(candidates, target);
+
+ assertEquals(1, result.size());
+ assertTrue(result.contains(Arrays.asList(1)));
+ }
+
+ @Test
+ void testSingleElementRepeated() {
+ CombinationSum cs = new CombinationSum();
+ int[] candidates = {2};
+ int target = 8;
+ List> result = cs.combinationSum(candidates, target);
+
+ assertEquals(1, result.size());
+ assertTrue(result.contains(Arrays.asList(2, 2, 2, 2)));
+ }
+
+ @Test
+ void testLargerNumbers() {
+ CombinationSum cs = new CombinationSum();
+ int[] candidates = {10, 1, 2, 7, 6, 1, 5};
+ int target = 8;
+ List> result = cs.combinationSum(candidates, target);
+
+ assertFalse(result.isEmpty());
+ // Verify all combinations sum to target
+ for (List combination : result) {
+ int sum = combination.stream().mapToInt(Integer::intValue).sum();
+ assertEquals(target, sum);
+ }
+ }
+
+ @Test
+ void testTargetZero() {
+ CombinationSum cs = new CombinationSum();
+ int[] candidates = {1, 2, 3};
+ int target = 0;
+ List> result = cs.combinationSum(candidates, target);
+
+ // Should return empty list in the combination
+ assertEquals(1, result.size());
+ assertTrue(result.get(0).isEmpty());
+ }
+
+ @Test
+ void testEmptyCandidates() {
+ CombinationSum cs = new CombinationSum();
+ int[] candidates = {};
+ int target = 5;
+ List> result = cs.combinationSum(candidates, target);
+
+ assertTrue(result.isEmpty());
+ }
+
+ @Test
+ void testLargeTarget() {
+ CombinationSum cs = new CombinationSum();
+ int[] candidates = {3, 5, 8};
+ int target = 11;
+ List> result = cs.combinationSum(candidates, target);
+
+ assertTrue(result.contains(Arrays.asList(3, 3, 5)));
+ assertTrue(result.contains(Arrays.asList(3, 8)));
+
+ // Verify all combinations sum to target
+ for (List combination : result) {
+ int sum = combination.stream().mapToInt(Integer::intValue).sum();
+ assertEquals(target, sum);
+ }
+ }
+
+ @Test
+ void testAllCombinationsValid() {
+ CombinationSum cs = new CombinationSum();
+ int[] candidates = {2, 3, 6, 7};
+ int target = 7;
+ List> result = cs.combinationSum(candidates, target);
+
+ // Verify each combination sums to target
+ for (List combination : result) {
+ int sum = 0;
+ for (int num : combination) {
+ sum += num;
+ }
+ assertEquals(target, sum, "Each combination should sum to target");
+ }
+
+ // Verify no duplicates in result
+ assertEquals(result.size(), result.stream().distinct().count());
+ }
+}
From 29e3dde72c69a18453ca902bf165e7adcdbe4832 Mon Sep 17 00:00:00 2001
From: Tejasrahane <161036451+Tejasrahane@users.noreply.github.com>
Date: Wed, 22 Oct 2025 10:21:18 +0530
Subject: [PATCH 3/4] Refactor CombinationSumTest to use static method
---
.../recursion/CombinationSumTest.java | 26 +++++++++----------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java b/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java
index f5d3d4fbccc3..228dde053215 100644
--- a/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java
+++ b/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java
@@ -49,10 +49,10 @@ void testNoSolution() {
@Test
void testSingleElement() {
- CombinationSum cs = new CombinationSum();
+
int[] candidates = {1};
int target = 1;
- List> result = cs.combinationSum(candidates, target);
+ List> result = CombinationSum.combinationSum(candidates, target);
assertEquals(1, result.size());
assertTrue(result.contains(Arrays.asList(1)));
@@ -60,10 +60,10 @@ void testSingleElement() {
@Test
void testSingleElementRepeated() {
- CombinationSum cs = new CombinationSum();
+
int[] candidates = {2};
int target = 8;
- List> result = cs.combinationSum(candidates, target);
+ List> result = CombinationSum.combinationSum(candidates, target);
assertEquals(1, result.size());
assertTrue(result.contains(Arrays.asList(2, 2, 2, 2)));
@@ -71,10 +71,10 @@ void testSingleElementRepeated() {
@Test
void testLargerNumbers() {
- CombinationSum cs = new CombinationSum();
+
int[] candidates = {10, 1, 2, 7, 6, 1, 5};
int target = 8;
- List> result = cs.combinationSum(candidates, target);
+ List> result = CombinationSum.combinationSum(candidates, target);
assertFalse(result.isEmpty());
// Verify all combinations sum to target
@@ -86,10 +86,10 @@ void testLargerNumbers() {
@Test
void testTargetZero() {
- CombinationSum cs = new CombinationSum();
+
int[] candidates = {1, 2, 3};
int target = 0;
- List> result = cs.combinationSum(candidates, target);
+ List> result = CombinationSum.combinationSum(candidates, target);
// Should return empty list in the combination
assertEquals(1, result.size());
@@ -98,20 +98,19 @@ void testTargetZero() {
@Test
void testEmptyCandidates() {
- CombinationSum cs = new CombinationSum();
+
int[] candidates = {};
int target = 5;
- List> result = cs.combinationSum(candidates, target);
+ List> result = CombinationSum.combinationSum(candidates, target);
assertTrue(result.isEmpty());
}
@Test
void testLargeTarget() {
- CombinationSum cs = new CombinationSum();
int[] candidates = {3, 5, 8};
int target = 11;
- List> result = cs.combinationSum(candidates, target);
+ List> result = CombinationSum.combinationSum(candidates, target);
assertTrue(result.contains(Arrays.asList(3, 3, 5)));
assertTrue(result.contains(Arrays.asList(3, 8)));
@@ -125,10 +124,9 @@ void testLargeTarget() {
@Test
void testAllCombinationsValid() {
- CombinationSum cs = new CombinationSum();
int[] candidates = {2, 3, 6, 7};
int target = 7;
- List> result = cs.combinationSum(candidates, target);
+ List> result = CombinationSum.combinationSum(candidates, target);
// Verify each combination sums to target
for (List combination : result) {
From 59d8bebc93e737ed1ffdd9a3b0e735b46d6a30d3 Mon Sep 17 00:00:00 2001
From: Tejasrahane <161036451+Tejasrahane@users.noreply.github.com>
Date: Wed, 22 Oct 2025 10:32:02 +0530
Subject: [PATCH 4/4] Fix CombinationSumTest to use static method calls
Removed all instances of 'new CombinationSum()' in testBasicCase, testMultipleCombinations, and testNoSolution methods. Updated all calls to use the static method CombinationSum.combinationSum() directly instead of creating an instance, as the CombinationSum class has a private constructor and is designed to be used statically.
---
.../recursion/CombinationSumTest.java | 22 +++----------------
1 file changed, 3 insertions(+), 19 deletions(-)
diff --git a/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java b/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java
index 228dde053215..337c6431a6bf 100644
--- a/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java
+++ b/src/test/java/com/thealgorithms/recursion/CombinationSumTest.java
@@ -1,52 +1,42 @@
package com.thealgorithms.recursion;
-
import static org.junit.jupiter.api.Assertions.*;
-
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
-
/**
* Comprehensive test class for CombinationSum algorithm
* Tests various scenarios including edge cases
*/
class CombinationSumTest {
-
@Test
void testBasicCase() {
- CombinationSum cs = new CombinationSum();
int[] candidates = {2, 3, 6, 7};
int target = 7;
- List> result = cs.combinationSum(candidates, target);
+ List> result = CombinationSum.combinationSum(candidates, target);
assertTrue(result.contains(Arrays.asList(2, 2, 3)));
assertTrue(result.contains(Arrays.asList(7)));
assertEquals(2, result.size());
}
-
@Test
void testMultipleCombinations() {
- CombinationSum cs = new CombinationSum();
int[] candidates = {2, 3, 5};
int target = 8;
- List> result = cs.combinationSum(candidates, target);
+ List> result = CombinationSum.combinationSum(candidates, target);
assertTrue(result.contains(Arrays.asList(2, 2, 2, 2)));
assertTrue(result.contains(Arrays.asList(2, 3, 3)));
assertTrue(result.contains(Arrays.asList(3, 5)));
assertEquals(3, result.size());
}
-
@Test
void testNoSolution() {
- CombinationSum cs = new CombinationSum();
int[] candidates = {2};
int target = 1;
- List> result = cs.combinationSum(candidates, target);
+ List> result = CombinationSum.combinationSum(candidates, target);
assertTrue(result.isEmpty());
}
-
@Test
void testSingleElement() {
@@ -57,7 +47,6 @@ void testSingleElement() {
assertEquals(1, result.size());
assertTrue(result.contains(Arrays.asList(1)));
}
-
@Test
void testSingleElementRepeated() {
@@ -68,7 +57,6 @@ void testSingleElementRepeated() {
assertEquals(1, result.size());
assertTrue(result.contains(Arrays.asList(2, 2, 2, 2)));
}
-
@Test
void testLargerNumbers() {
@@ -83,7 +71,6 @@ void testLargerNumbers() {
assertEquals(target, sum);
}
}
-
@Test
void testTargetZero() {
@@ -95,7 +82,6 @@ void testTargetZero() {
assertEquals(1, result.size());
assertTrue(result.get(0).isEmpty());
}
-
@Test
void testEmptyCandidates() {
@@ -105,7 +91,6 @@ void testEmptyCandidates() {
assertTrue(result.isEmpty());
}
-
@Test
void testLargeTarget() {
int[] candidates = {3, 5, 8};
@@ -121,7 +106,6 @@ void testLargeTarget() {
assertEquals(target, sum);
}
}
-
@Test
void testAllCombinationsValid() {
int[] candidates = {2, 3, 6, 7};