3

I'm trying to find a fast way to solve the subset sum problem with a few modifications, I know the exact size of the subset I need to get the target number and I also know the input array to be a range from 1 to 2000. My questions is if there is any way to improve upon the base subset sum problem solution to make it even faster when knowing these conditions as the normal solutions are too slow. Basically the only changing part is the wanted target sum.

I would preferably want it to return all the possible subsets of the given size that add up to the target value if its possible without slowing the program down too much. An example code in python or a similar language would be appriciated.

I've tried many of the solutions for the base subset sum problem but they are too slow to execute due to the size of the input array.

Fanfer123
  • 67
  • 11
  • What do you mean when you say that the input array is "a range from 1 to 2000"? Do you mean that it is [1, 2, 3, ... 2000]? – Matt Timmermans Nov 07 '22 at 12:23
  • @MattTimmermans yes, that is what i mean. – Fanfer123 Nov 07 '22 at 14:12
  • Are you being asked to find a solution or to count the number of possible solutions? – rici Nov 07 '22 at 14:28
  • @rici Well in my question I did say that I would preferably want it to return all the possible subsets of the given size that add up to the target value if its possible without slowing down the program too much – Fanfer123 Nov 07 '22 at 14:34
  • @Fanfer123: counting solutions and generating all solutions are very different in combinatorial problems. Generating an exponential number if solutions is necessarily slow (for the number of solutions) but it is often possible to count them very efficiently. – rici Nov 07 '22 at 15:07

2 Answers2

1

Knowing the size of the subset is an incredibly powerful information, because you don't have to iterate through subset size.

Given N your subset size, you could just :

  • Sum up the N first elements of your input array (first subset of size N)
  • Iterate by substracting the first element of your subarray, and adding the element next to it, which translate to looking at the next subarray
  • Return the subarray if the sum equals your target number

This should be O(input array size) in time and O(1) in memory, regardless of the initial array content. There is probably a more optimal solution using the range property of your initial array.

Here is an example in C++ :

void subsetSum(std::vector<int>() array, int subArraySize, int targetNumber)
{
    int sum = 0;
    for (int i = 0; i < subArraySize; ++i) // Initial sum
    {
        sum += array[i];
    }
    for (int i = subArraySize; i < array.size(), ++i)
    {
        sum -= array[subArraySize-i];
        sum += array[i];
        if (sum == targetNumber)
            std::cout << subArraySize-i; // this print the starting position of the subarray
    }
}
DrosvarG
  • 483
  • 3
  • 14
  • I'm sorry if the question was not clear enough but the subset that is the answer is not neccecarily in order in the input array, meaning the answer subset can contain the first element and the last element in the initial input array for example – Fanfer123 Nov 07 '22 at 10:21
  • Mea culpa, I mixed subarray and subset. I'm gonna look for a solution for finding the subset, and I'll edit this post – DrosvarG Nov 07 '22 at 10:47
  • Sure let me know when you do – Fanfer123 Nov 07 '22 at 12:14
0

First find the contiguous subarray that solves this, or as close to contiguous as we can get. The center of this is going to be target/width if width is odd, or (target-1)/width, (target+1)/width if width is even.

Having found the center, add the same number of neighbors on both sides until you get to the desired width. The rightmost element of the array will need to be shifted further right in cases where there is no contiguous solution.

Ruby code:

def f(target, width)
  arr = []

  # put in center of array
  if width % 2 == 0
    arr.append target / width
    arr.append target / width + 1
  else
    arr.append target/width
  end
  
  # repeatedly prepend next smallest integer
  # and append next largest integer
  while arr.length < width
    arr.unshift(arr[0] - 1)
    arr.append(arr[-1] + 1)
  end
  
  # increase the last element of the array to match
  # the target sum. This is only necessary if there is no
  # contiguous solution. Because of integer division, 
  # where we need to adjust it will always be to increase
  # the sum of the array.
  arr[-1] += target - arr.sum
  
  return arr
end

Example run:
> f(12342, 7)
=> [1760, 1761, 1762, 1763, 1764, 1765, 1767]

Note that this code doesn't do any of the work of confirming that a solution exists in the range (1, 2000), but your code should.

So far so fast, but finding all subsets that solve this will be slow because there are many. You can find them by pushing elements to the left and right. in pairs.

Final answer will be the sum over i of: (number of ways of pushing elements to the left by a cumulative i spaces) (number of ways of pushing elements to the right by a cumulative i spaces.

To give a simple example: for a target of 13, width of 3, we start with [3,4,6].

pushes: arrays
0: [3, 4, 6]
1: [2, 4, 7], [2, 5, 6]
2: [1, 4, 8], [1, 5, 7], [2, 3, 8]
3: [1, 3, 9]
4: [1, 2, 10]

... and we're done. There will be a massive number of these, peaking (I think) when the width of the array is half the width of the range, and the initial array is centered in the range.

Dave
  • 7,460
  • 3
  • 26
  • 39
  • Say in the last step of my algorithm we increase the last element of the array by k. You might prefer to instead increase the last k elements by 1. This guarantees that the last element of the array is within your range if there is any solution in your range. – Dave Nov 07 '22 at 15:15