diff --git a/src/main/java/com/thealgorithms/backtracking/CombinationSum.java b/src/main/java/com/thealgorithms/backtracking/CombinationSum.java new file mode 100644 index 000000000000..09b99032bdc1 --- /dev/null +++ b/src/main/java/com/thealgorithms/backtracking/CombinationSum.java @@ -0,0 +1,48 @@ +package com.thealgorithms.backtracking; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** Backtracking: pick/not-pick with reuse of candidates. */ +public final class CombinationSum { + private CombinationSum() { + throw new UnsupportedOperationException("Utility class"); + } + + public static List> combinationSum(int[] candidates, int target) { + List> results = new ArrayList<>(); + if (candidates == null || candidates.length == 0) { + return results; + } + + // Sort to help with pruning duplicates and early termination + Arrays.sort(candidates); + backtrack(candidates, target, 0, new ArrayList<>(), results); + return results; + } + + private static void backtrack(int[] candidates, int remaining, int start, List combination, List> results) { + if (remaining == 0) { + // Found valid combination; add a copy + results.add(new ArrayList<>(combination)); + return; + } + + for (int i = start; i < candidates.length; i++) { + int candidate = candidates[i]; + + // If candidate is greater than remaining target, further candidates (sorted) will also be too big + if (candidate > remaining) { + break; + } + + // include candidate + combination.add(candidate); + // Because we can reuse the same element, we pass i (not i + 1) + backtrack(candidates, remaining - candidate, i, combination, results); + // backtrack: remove last + combination.remove(combination.size() - 1); + } + } +} diff --git a/src/test/java/com/thealgorithms/backtracking/CombinationSumTest.java b/src/test/java/com/thealgorithms/backtracking/CombinationSumTest.java new file mode 100644 index 000000000000..986c71acebe8 --- /dev/null +++ b/src/test/java/com/thealgorithms/backtracking/CombinationSumTest.java @@ -0,0 +1,29 @@ +package com.thealgorithms.backtracking; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import org.junit.jupiter.api.Test; + +class CombinationSumTest { + private static List> norm(Iterable> x) { + List> y = new ArrayList<>(); + for (var p : x) { + var q = new ArrayList<>(p); + q.sort(Integer::compare); + y.add(q); + } + y.sort(Comparator.>comparingInt(List::size).thenComparing(Object::toString)); + return y; + } + + @Test + void sample() { + int[] candidates = {2, 3, 6, 7}; + int target = 7; + var expected = List.of(List.of(2, 2, 3), List.of(7)); + assertEquals(norm(expected), norm(CombinationSum.combinationSum(candidates, target))); + } +}