3

I have a simple algorithmic question:

If I have certain elements that integer values like:

1 1 1 1 1 1 1 1 1 1 1 1 10 12 2

and I have to make the sum 12, the minimum number of elements needed would 1, I would just use 12.

Thus, my question is how would you: find the minimum number of elements to make some sum, and if you can't output -1.

Please suggest an algorithm I can look into so I can solve this efficiently. I've already tried brute force but it is much to slow for my needs.

user3188300
  • 341
  • 2
  • 10
  • Exactly 12? or at least 12? – Strawberry Mar 09 '14 at 22:44
  • I'm quite surprised that your brute force is so slow. Maybe you've been too much brute. Do you sort your input array ? If so it is easy to stop looking further when the sum is too large. – hivert Mar 09 '14 at 22:50
  • Exactly 12. But I want a general algorithm for any size that is pretty efficient. – user3188300 Mar 09 '14 at 22:58
  • 1
    In general you cannot solve this efficiently because it is NP-Complete. It is the subset sum problem. – AndyG Mar 09 '14 at 23:57
  • This would be better asked in the Computer science site http://cs.stackexchange.com/ – SeeMoreGain Mar 10 '14 at 00:22
  • This looks like the change making problem? [link](http://en.wikipedia.org/wiki/Change-making_problem), where the sum is the amount of money you are going to give and the array elements are the coins. Use DP to generate an array where `dp[i]` is the minimum number of elements which sum to value `i`. Time and space complexity is O(sum). – Jason L Mar 10 '14 at 02:07
  • @JasonL: Almost. The change-making problem assumes 2 things, the more important one is that you may reuse elements from your set. The other is that you know a priori what your denominations are, which is usually a small k-value anyway. – AndyG Mar 10 '14 at 05:42

4 Answers4

3

The problem is np-complete and can be reduced to subset sum or knapsack problem. There is pseudo polynomial time algorithm that can solve it using dynamic programming. Following is a solution similar to knapsack analogy:-

1. Knapsack capacity = Sum
2. Items have same weight and value
3. Maximize profit 
4. if max_profit == Sum then there is a solution
5. else Sum cannot be made from the items given.
6. Evaluate the minimum items needed using matrix alongside the DP.
7. Can also reconstruct all solutions and get the minimum one.

Time Complexity : - O(Sum*Items)

Java Implementation :-

public class SubSetSum {
    static int[][] costs;
    static int[][] minItems;

    public static void calSets(int target,int[] arr) {

        costs = new int[arr.length][target+1];
        minItems = new int[arr.length][target+1];
        for(int j=0;j<=target;j++) {
            if(arr[0]<=j) {

                costs[0][j] = arr[0]; 
                minItems[0][j] = 1;
            }
        }
        for(int i=1;i<arr.length;i++) {

            for(int j=0;j<=target;j++) {
                costs[i][j] = costs[i-1][j];
                minItems[i][j] = minItems[i-1][j];
                if(arr[i]<=j) {
                    costs[i][j] = Math.max(costs[i][j],costs[i-1][j-arr[i]]+arr[i]);
                    if(costs[i-1][j]==costs[i-1][j-arr[i]]+arr[i]) {

                        minItems[i][j] = Math.min(minItems[i][j],minItems[i-1][j-arr[i]]+1);
                    }
                    else if(costs[i-1][j]<costs[i-1][j-arr[i]]+arr[i]) {
                        minItems[i][j] = minItems[i-1][j-arr[i]]+1;
                    }
                }
            }

        }

       // System.out.println(costs[arr.length-1][target]);
       if(costs[arr.length-1][target]==target) {

           System.out.println("Minimum items need : "+minItems[arr.length-1][target]);

       } 

       else System.out.println("No such Set found");

    } 



    public static void main(String[] args) {
        int[] arr = {1,1,1,1, 1 ,1 ,1, 1 ,1, 1 ,1 ,1, 10 ,12, 2};
        calSets(12, arr);

    }
}
smac89
  • 39,374
  • 15
  • 132
  • 179
Vikram Bhat
  • 6,106
  • 3
  • 20
  • 19
0

here is a recursive approach that should be rather fast:

1) if your input vector is of length 1, either return 1 if the value is equal the target, or return -1 if it doesn't. similarly, if your target is less than any of your items in your input vector, return -1. 2) otherwise, loop on (unique) values in your input vector (in descending order, for performance): 2a) remove the value for your vector, and substract it from your target. 2b) recursively call this function on the new vector and the new target note: you can pass down the algorithm a max.step parameter, so that if you have already found a solution with length K, you would stop the recursive calls at that depth, but not beyond. remember to decrease your max.step value in each recursive call. 3) collect all the values from the recursive calls, take the minimum (which is not -1) and add 1 to it and return, or, if all values in the loop are -1, return -1.

amit
  • 3,332
  • 6
  • 24
  • 32
  • And what is the time complexity? We cannot do better than exponential for an exact solution. – AndyG Mar 09 '14 at 23:59
  • It is clearly exponential in the worst case. Think for example of the case where the list doesn't contains any `1`, have only different values and your goal is `S-1` (which is not feasible) where `S` is the sum of the list. – hivert Mar 10 '14 at 00:21
0

Disclaimer: This is an advertisement for nice but relatively simple mathematics which leads to very clever and fast counting formulas and algorithms. I'm aware that you can find a much simpler and efficient solution using usual programming. I just like the fact that using properly a Computer Algebra System you can do it in a one liner: Lets get 19 with this list:

sage: l = [1,1,1,2,5,2,1,3,12,1,3]; goal = 19
sage: prod((1+t*x^i) for i in l).expand().collect(x).coefficient(x,goal).low_degree(t)
3

What about 25:

sage: goal=25
sage: prod((1+t*x^i) for i in l).expand().collect(x).coefficient(x,goal).low_degree(t)
5

36 is not feasible:

sage: goal=36
sage: prod((1+t*x^i) for i in l).expand().collect(x).coefficient(x,goal).low_degree(t)
0

Here are some details: Just expand the product

(1+t*x^l[0]) (1+t*x^l[1]) ... (1+t*x^l[n])

Where your list is l. Then to find the minimum number of element required to get the sum S, collect the coefficients of x^S and return the minimum degree of a term in t.

Here is how it could be done in :

sage: var("x t")
(x, t)
sage: l = [1,1,1,2,5,2,1,3,12,1,3]
sage: s = prod((1+t*x^i) for i in l)
sage: s = expand(s).collect(x)

Now

sage: print(s)
t^11*x^32 + 5*t^10*x^31 + 2*(t^10 + 5*t^9)*x^30 + 2*(t^10 + 5*t^9 + 5*t^8)*x^29 + (11*t^9 + 20*t^8 + 5*t^7)*x^28 + (t^10 + 4*t^9 + 25*t^8 + 20*t^7 + t^6)*x^27 + 2*(3*t^9 + 10*t^8 + 15*t^7 + 5*t^6)*x^26 + (2*t^9 + 17*t^8 + 40*t^7 + 20*t^6 + 2*t^5)*x^25 + (2*t^9 + 12*t^8 + 30*t^7 + 40*t^6 + 7*t^5)*x^24 + (11*t^8 + 30*t^7 + 35*t^6 + 20*t^5 + t^4)*x^23 + 2*(2*t^8 + 13*t^7 + 20*t^6 + 13*t^5 + 2*t^4)*x^22 + (t^8 + 20*t^7 + 35*t^6 + 30*t^5 + 11*t^4)*x^21 + (t^10 + 7*t^7 + 40*t^6 + 30*t^5 + 12*t^4 + 2*t^3)*x^20 + (5*t^9 + 2*t^7 + 20*t^6 + 40*t^5 + 17*t^4 + 2*t^3)*x^19 + 2*(t^9 + 5*t^8 + 5*t^6 + 15*t^5 + 10*t^4 + 3*t^3)*x^18 + (2*t^9 + 10*t^8 + 10*t^7 + t^6 + 20*t^5 + 25*t^4 + 4*t^3 + t^2)*x^17 + (11*t^8 + 20*t^7 + 5*t^6 + 5*t^5 + 20*t^4 + 11*t^3)*x^16 + (t^9 + 4*t^8 + 25*t^7 + 20*t^6 + t^5 + 10*t^4 + 10*t^3 + 2*t^2)*x^15 + 2*(3*t^8 + 10*t^7 + 15*t^6 + 5*t^5 + 5*t^3 + t^2)*x^14 + (2*t^8 + 17*t^7 + 40*t^6 + 20*t^5 + 2*t^4 + 5*t^2)*x^13 + (2*t^8 + 12*t^7 + 30*t^6 + 40*t^5 + 7*t^4 + t)*x^12 + (11*t^7 + 30*t^6 + 35*t^5 + 20*t^4 + t^3)*x^11 + 2*(2*t^7 + 13*t^6 + 20*t^5 + 13*t^4 + 2*t^3)*x^10 + (t^7 + 20*t^6 + 35*t^5 + 30*t^4 + 11*t^3)*x^9 + (7*t^6 + 40*t^5 + 30*t^4 + 12*t^3 + 2*t^2)*x^8 + (2*t^6 + 20*t^5 + 40*t^4 + 17*t^3 + 2*t^2)*x^7 + 2*(5*t^5 + 15*t^4 + 10*t^3 + 3*t^2)*x^6 + (t^5 + 20*t^4 + 25*t^3 + 4*t^2 + t)*x^5 + (5*t^4 + 20*t^3 + 11*t^2)*x^4 + 2*(5*t^3 + 5*t^2 + t)*x^3 + 2*(5*t^2 + t)*x^2 + 5*t*x + 1

Ok this is a huge expression. The nice feature here is that If I take the coefficient say of x^17 I get:

sage: s.coefficient(x, 17)
2*t^9 + 10*t^8 + 10*t^7 + t^6 + 20*t^5 + 25*t^4 + 4*t^3 + t^2

which says the following: the term 10*t^7 tells me that there are 10 different way to obtains the sum 17 using 7 number. Another example, there are 25 way to get 17 using 4 number (25*t^4).

Also since this expression ends with t^2 I learn that I only need two number to get 17. Unfortunately this doesn't tells which numbers.

If you want to understand the trick, look at Wikipedia article on generating functions and This Page.

Note 1: this is not the most efficient since I compute much more than what you need. The huge expression actually described and somehow computed all possible choices (that is 2^the length of the list). But it's a one liner:

sage: prod((1+t*x^i) for i in l).expand().collect(x).coefficient(x,17).low_degree(t)
2

And still relatively efficient:

sage: %timeit prod((1+t*x^i) for i in l).expand().collect(x).coefficient(x,17).low_degree(t)
10 loops, best of 3: 42.6 ms per loop

Note 2: After thinking carefully about it I also realized the following: Generating series is just a compact encoding of what you would have written if you tried to implement a dynamic programming solution.

hivert
  • 10,579
  • 3
  • 31
  • 56
  • Please provide a time complexity. When you say "I'm aware that you can find a much simpler and efficient solution using usual programming" please describe what you mean. As far as I understand, this problem is clearly NP-Complete. – AndyG Mar 10 '14 at 00:02
  • The complexity of this solution is somewhere near `2^(length of the list)` if you count coefficients operations as constant time. For the problem itself, it is indeed NP complete (as some kind of knapsack problem). By my remark, I meant that by computing `s`, I'm solving **once for all** the problem for all possible value of the goal which is clearly suboptimal. – hivert Mar 10 '14 at 00:10
  • Thank you. Your post implies there is a polynomial time solution, though, which we can't know until someone solves the P = NP problem. – AndyG Mar 10 '14 at 00:12
  • Please see my edit. I didn't implied that there is a polymial time solution. – hivert Mar 10 '14 at 00:13
0

I don't think this solution is optimal, but it's very easy to understand and use, you sort the elements in decreasing order, then you take each element and try to fit it in your number. If you have the sequence [5,6,2,7] and you need to make the 15 number, you'll reorder the sequence [7,6,5,2] and take 7, then you need to extract 8 so you'll take 6, then you'll need 2 more, check 5 but it's too big and you'll skip it and check the last number, 2, which it's perfect and finishes your number. So you'd print out 3. This is the worst case of the algorithm which is O(n). But in your example with 12, it'll be O(1), because you'll pick 12 from the first checkup of the ordered sequence. (running time applies only for the program of choosing items, not sorting)

resolve_sum(ordered_items[], number) {
    count = 0;
    aux = number;
    i = 0;
    while (aux - ordered_items[i] <= 0) {
         count = count + 1;
         aux = aux - ordered_items[i];
         i = i + 1;
    }
    if (aux == 0) return count;
    else return -1;
}

I haven't included an algorithm for sorting, you can choose one that you know best or try to learn a new efiecient one. Link with sorting algorithms and their running time. This is just a sample code you can use in C/C++ or Java or what you need. I hope it isn't way too much brute force.

student0495
  • 171
  • 3
  • 15
  • actually I've made a mistake, in while the condition is `aux > 0` and then inside it you check `if (aux - ordered_items[i] <= 0) {count = count + 1; aux = aux - ordered_items[i];}` and then you just increment i – student0495 May 09 '16 at 14:21