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. – Redu