By the name of it, it seems like a classic Dynamic Programming problem, but as soon as you pay attention to the constraints, it soon becomes evident that a DP solution which will most optimally have O(N*W) complexity, where N = number of elements and W= the target sum range's upper bound, will bottleneck at W.
I solved this problem using Meet in the middle technique whereby, I divide the set of input numbers into 2 halves and consider sums of all subsets in each half (cardinality 2^17 in each set, which can be pretty easily generated using brute-force recursion), to find the subset-duos (containing one subset from each half) who sum up to fall in the given range. The count of such possible subset-duos gives us our solution. My solution also involves putting binary search and recursion into use.
Here's my accepted(https://www.spoj.com/status/SUBSUMS,chandniverma/) solution code:
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Scanner; import java.util.function.BiPredicate; public class Main{ public static void main(String args[]) { Scanner sc = new Scanner(System.in); int N = sc.nextInt(); int A = sc.nextInt(); int B = sc.nextInt(); int arr[] = new int[N]; for (int i=0; i<N; i++) { arr[i] = sc.nextInt(); } sc.close(); System.out.println(getCountOfSubsets(arr, A, B)); } private static long getCountOfSubsets(int[] arr, int a, int b) { Integer[] firstSubsetSums = getSubsetSums(arr, 0, arr.length/2); Integer[] secondSubsetSums = getSubsetSums(arr, arr.length/2+1, arr.length-1); Arrays.sort(secondSubsetSums); long count = 0; for(int i=0; i<firstSubsetSums.length; i++) { int p = findLastIdxWithFalsePredicate(secondSubsetSums, a-firstSubsetSums[i], (sum, mark)->sum>=mark); int q = findLastIdxWithFalsePredicate(secondSubsetSums, b-firstSubsetSums[i], (sum, mark)->sum>mark); count += (q-p); } return count; } private static int findLastIdxWithFalsePredicate(Integer[] sums, int val, BiPredicate<Integer, Integer> pred) { int min = 0; int max = sums.length-1; while (min<max) { int mid = min + (max-min+1)/2; if (pred.test(sums[mid], val)) { max = mid-1; } else { min = mid; } } if (pred.test(sums[min], val)) return -1; return min; } private static Integer[] getSubsetSums(int[] arr, int st, int end) { List<Integer> sums = new ArrayList<>(); generateSubsetSumsRecur(arr, st, end, st, 0, sums); return sums.toArray(new Integer[0]); } private static void generateSubsetSumsRecur(int[] arr, int st, int end, int index, int runningSum, List<Integer> sums) { if (index == end+1) { sums.add(runningSum); return; } generateSubsetSumsRecur(arr, st, end, index+1, runningSum+arr[index], sums); generateSubsetSumsRecur(arr, st, end, index+1, runningSum, sums); } }
The complexity of the above code is:
O(2^(N/2) + 2^(N/2)*(lg (2^(N/2))))
= O(2^(N/2) + 2^(N/2)*N/2)
= O(N*2^(N/2))
Feel free to checkout and let me know your thoughts in the comments below!
Do share if you have a better solution in mind!
No comments:
Post a Comment