Since you are using Java, I would add an implementation in Java, using dynamic programming (it's recursion, in this case).
Code
SubSumDupComb.java:
import java.util.*; public class SubSumDupComb { /** * Find shortest combination add to given sum. * * @param arr input array, * @param sum target sum, * @return */ public static int[] find(int[] arr, int sum) { // System.out.printf("input arr: %s, sum: %d\n", Arrays.toString(arr), sum); List<Integer> list = find(arr, 0, sum, new ArrayList<>()); // System.out.printf("result: %s\n", list); return listToArray(list); } /** * Find shortest combination add to given sum, start from given index. * * @param arr input array, * @param start start index, for further search, * @param sum remain sum, * @param prefixList prefix list, * @return */ private static List<Integer> find(int[] arr, int start, int sum, List<Integer> prefixList) { if (sum == 0) return prefixList; // base case, if (start >= arr.length || sum < 0) return null; // bad case, // exclude current index, List<Integer> shortestExcludeList = find(arr, start + 1, sum, prefixList); // include current index, List<Integer> includePrefixList = new ArrayList<>(prefixList); includePrefixList.add(arr[start]); List<Integer> shortestIncludeList = find(arr, start, sum - arr[start], includePrefixList); if (shortestIncludeList == null && shortestExcludeList == null) return null; // both null, if (shortestIncludeList != null && shortestExcludeList != null) // both non-null, return shortestIncludeList.size() < shortestExcludeList.size() ? shortestIncludeList : shortestExcludeList; // prefer to include elements with larger index, else return shortestIncludeList == null ? shortestExcludeList : shortestIncludeList; // exactly one null, } /** * Find shortest combination add to given sum, with cache. * * @param arr input array, * @param sum target sum, * @return */ public static int[] findWithCache(int[] arr, int sum) { // System.out.printf("input arr: %s, sum: %d\n", Arrays.toString(arr), sum); List<Integer> list = findWithCache(arr, 0, sum, new ArrayList<>(), new HashMap<>()); // System.out.printf("result: %s\n", list); return listToArray(list); } /** * Find shortest combination add to given sum, start from given index, with cache. * * @param arr input array, * @param start start index, for further search, * @param sum remain sum, * @param prefixList prefix list, * @return */ private static List<Integer> findWithCache(int[] arr, int start, int sum, List<Integer> prefixList, Map<Integer, Map<Integer, List<Integer>>> cache) { if (sum == 0) return prefixList; // base case, if (start >= arr.length || sum < 0) return null; // bad case, // check cache, Map<Integer, List<Integer>> cacheAtStart; if ((cacheAtStart = cache.get(start)) != null && cacheAtStart.containsKey(sum)) { // cache hit, tips: the cashed list could be null, which indicate no result, // System.out.printf("hit cache: start = %d, sum = %d, cached list: %s\n", start, sum, cacheAtStart.get(sum)); return cacheAtStart.get(sum); } // exclude current index, tips: should call this first, List<Integer> shortestExcludeList = findWithCache(arr, start + 1, sum, prefixList, cache); // include current index, List<Integer> includePrefixList = new ArrayList<>(prefixList); includePrefixList.add(arr[start]); List<Integer> shortestIncludeList = findWithCache(arr, start, sum - arr[start], includePrefixList, cache); List<Integer> resultList; if (shortestIncludeList == null && shortestExcludeList == null) resultList = null; // both null, else if (shortestIncludeList != null && shortestExcludeList != null) // both non-null, resultList = shortestIncludeList.size() < shortestExcludeList.size() ? shortestIncludeList : shortestExcludeList; // prefer to include elements with larger index, else resultList = (shortestIncludeList == null ? shortestExcludeList : shortestIncludeList); // exactly one null, // add to cache, if (cacheAtStart == null) { // init cache at given start, cacheAtStart = new HashMap<>(); cache.put(start, cacheAtStart); } cacheAtStart.put(sum, resultList == null ? null : resultList); // add this result to cache, // System.out.printf("add cache: start = %d, sum = %d, list: %s\n", start, sum, resultList); return resultList; } /** * List to array. * * @param list * @return */ private static int[] listToArray(List<Integer> list) { if (list == null) return null; // no solution, // list to array, int[] result = new int[list.size()]; for (int i = 0; i < list.size(); i++) { result[i] = list.get(i); } return result; } }
SubSumDupCombTest.java:
(Test case, via TestNG)
import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.util.Arrays; public class SubSumDupCombTest { private int[] arr; private int sum; private int[] expectedResultArr; private int[] arr2; private int sum2; private int sum2NoSolution; private int[] expectedResultArr2; @BeforeClass public void setUp() { // init - arr, arr = new int[]{1, 4, 10, 20, 35}; sum = 17; expectedResultArr = new int[]{1, 4, 4, 4, 4}; Arrays.sort(expectedResultArr); // init - arr2, arr2 = new int[]{14, 6, 10}; sum2 = 40; sum2NoSolution = 17; expectedResultArr2 = new int[]{10, 10, 10, 10}; Arrays.sort(expectedResultArr2); } @Test public void test_find() { // arr int[] resultArr = SubSumDupComb.find(arr, sum); Arrays.sort(resultArr); Assert.assertTrue(Arrays.equals(resultArr, expectedResultArr)); // arr2 int[] resultArr2 = SubSumDupComb.find(arr2, sum2); Arrays.sort(resultArr2); Assert.assertTrue(Arrays.equals(resultArr2, expectedResultArr2)); } @Test public void test_find_noSolution() { Assert.assertNull(SubSumDupComb.find(arr2, sum2NoSolution)); } @Test public void test_findWithCache() { // arr int[] resultArr = SubSumDupComb.findWithCache(arr, sum); Arrays.sort(resultArr); Assert.assertTrue(Arrays.equals(resultArr, expectedResultArr)); // arr2 int[] resultArr2 = SubSumDupComb.findWithCache(arr2, sum2); Arrays.sort(resultArr2); Assert.assertTrue(Arrays.equals(resultArr2, expectedResultArr2)); } @Test public void test_findWithCache_noSolution() { Assert.assertNull(SubSumDupComb.findWithCache(arr2, sum2NoSolution)); } }
Explanation:
find()
It's pure recursion.
Compleixty:
- Time::
O(t^n) // in worst case, - Space:
O(n) // used by recursive method stack,
Where:
t, is average time of an element is included.
findWithCache()
Use cache for each pair of (start, sum).
Compleixty:
- Time::
O(n * s) - Space:
O(n * s) // used by cache, and recursive method stack,
Where:
s, is count of possible sum in the middle.
Tips:
- The result prefer numbers with larger indices, when there are multiple possible shortest results.
[1,4,10,20,35,56,84]and target being69. Just filtered the result set withresult.reduce((p,c) => p.length <= c.length ? p : c);and it returned[4, 10, 20, 35]in 32 ms. Sorry again that i don't have any Java knowledge to present it's only JavaScript.