26

First off, let me say that this is not homework (I am an A-Level student, this is nothing close to what we problem solve (this is way harder)), but more of a problem I'm trying to suss out to improve my programming logic.

I thought of a scenario where there is an array of random integers, let's for example say 10 integers. The user will input a number he wants to count to, and the algorithm will try and work out what numbers are needed to make that sum. For example if I wanted to make the sum 44 from this array of integers:

myIntegers = array(1, 5, 9, 3, 7, 12, 36, 22, 19, 63);

The output would be:

36 + 3 + 5 = 44

Or something along those lines. I hope I make myself clear. As an added bonus I would like to make the algorithm pick as few numbers as possible to make the required sum, or give out an error if the sum cannot be made with the numbers supplied.

I thought about using recursion and iterating through the array, adding numbers over and over until the sum is met or gone past. But what I can't get my head around is what to do if the algorithm goes past the sum and needs to be selective about what numbers to pick from the array.

I'm not looking for complete code, or a complete algorithm, I just want your opinions on how I should proceed with this and perhaps share a few tips or something. I'll probably start work on this tonight. :P

As I said, not homework. Just me wanting to do something a bit more advanced.

Thanks for any help you're able to offer. :)

genesis
  • 50,477
  • 20
  • 96
  • 125
Phox
  • 499
  • 3
  • 9
  • 12
  • 4
    see this: http://en.wikipedia.org/wiki/Subset_sum_problem – codaddict Feb 23 '10 at 17:14
  • 9
    Had this in CS course. Still think it's homework. ;) – Arnis Lapsa Feb 23 '10 at 17:18
  • 2
    Congratulations on coming up with NP-complete problem on your own! :) – Nikolai Fetissov Feb 23 '10 at 17:21
  • @Arnis: Well you're wrong. ;) – Phox Feb 23 '10 at 17:21
  • 1
    @Phox luckily - it doesn't matters at all. :D – Arnis Lapsa Feb 23 '10 at 17:26
  • lol @ being way harder than your hw problems. This is a pretty standard first year CS assignment. – Erix Feb 23 '10 at 18:09
  • Isn't this similar to what the better money-giving machines do (such as self-service checkout machines in supermarkets) when calculating what amounts of change you need from what you put in? :) – Chris Dennett Feb 23 '10 at 18:16
  • @Chris Good comparison, but currencies have the significant advantage that they are 'minimal' - that is, there is only one set of coins that adds up to any given total, and that way can be found by a simple greedy algorithm - start with the highest denomination, add until the remainder is less than the denomination, then step down to the next largest denomination and repeat. – Nick Johnson Feb 25 '10 at 10:19
  • @NickJohnson: Yes, the simple greedy algorithm works great for all real-world currencies I know of. But I wouldn't say "there is only one set of coins that adds up to any given total", since quarter+nickel = dime+dime+dime. – David Cary Dec 19 '12 at 15:47

10 Answers10

31

You are looking at the Knapsack Problem

The knapsack problem or rucksack problem is a problem in combinatorial optimization: Given a set of items, each with a weight and a value, determine the number of each item to include in a collection so that the total weight is less than a given limit and the total value is as large as possible. It derives its name from the problem faced by someone who is constrained by a fixed-size knapsack and must fill it with the most useful items.


Edit: Your special case is the Subset Sum Problem

Otto Allmendinger
  • 27,448
  • 7
  • 68
  • 79
  • 15
    Don't you love it when people come here asking how to quickly solve NP-Complete problems? – Poindexter Feb 23 '10 at 17:18
  • Thanks. I thought there would already be documentation on this somewhere but didn't know what to search to find them. :) – Phox Feb 23 '10 at 17:23
  • Just as a side note, the same problem solving alghorithm is used in most food packing factories where from up to 10 containers a single one is filled up with the required minimal weight. The aim is to get it right or fill a minimal more then required. – Drejc Feb 23 '10 at 20:08
  • @Poindexter: In this case, it's *weakly* NP-complete problems. – Rafał Dowgird Feb 23 '10 at 22:17
10

Will subset sum do? ;]

Nick Dandoulakis
  • 42,588
  • 16
  • 104
  • 136
pablochan
  • 5,625
  • 27
  • 42
4

This is the classic Knapsack problem that you would see in college level algorithms course (or at least I saw it then). Best to work this out on paper and the solution in code should be relatively easy to work out.

EDIT: One thing to consider is dynamic programming.

nevets1219
  • 7,692
  • 4
  • 32
  • 47
2

Your Problem is related to the subset sum problem. You have to try all possible combinations in the worst case.

swegi
  • 4,046
  • 1
  • 26
  • 45
2

No shortcuts here I'm afraid. In addition to what other people have said, about what specific problem this is etc., here's some practical advice to offer you a starting point:

I would sort the array and given the input sum m, would find the first number in the array less than m, call it n (this is your first possible number for the sum), and start from the highest possible complement (m-n), working your way down.

If you don't find a precise match, pick the highest available, call it o, (that now is your 2nd number) and look for the 3rd one starting from (m-n-o) and work your way down again.

If you don't find a precise match, start with the next number n (index of original n at index-1) and do the same. You can keep doing this until you find a precise match for two numbers. If no match for the sum is found for two numbers, start the process again, but expand it to include a 3rd number. And so on.

That could be done recursively. At least this approach ensures that when you find a match, it will be the one with the least possible numbers in the set forming the total input sum.

Potentially though, worst case, you end up going through the whole lot.

Edit: As Venr correctly points out, my first approach was incorrect. Edited approach to reflect this.

Wim
  • 11,998
  • 1
  • 34
  • 57
  • This approach does not guarantee you will find the answer with the least possible items, consider the set { 1, 2, 4, 6, 7 } and a desired sum of 10. with this approach you would end up with 7 + 2 + 1 but the answer you really want is 6 + 4. – Venr Feb 23 '10 at 17:44
  • Yes, good point @Venr. That's what happens when just thinking off the top your head without writing down a few examples. Still, will leave the answer here as it provides some kind of approach. – Wim Feb 23 '10 at 17:57
2

There is a very efficient randomized algorithm for this problem. I know you already accepted an answer, but I'm happy to share anyway, I just hope people will still check this question :).

Let Used = list of numbers that you sum.
Let Unused = list of numbers that you DON'T sum.
Let tmpsum = 0.
Let S = desired sum you want to reach.

for ( each number x you read )
  toss a coin:
    if it's heads and tmpsum < S
      add x to Used
    else
      add x to Unused

while ( tmpsum != S )
  if tmpsum < S 
    MOVE one random number from Unused to Used
  else
    MOVE one random number from Used to Unused

print the Used list, containing the numbers you need to add to get S

This will be much faster than the dynamic programming solution, especially for random inputs. The only problems are that you cannot reliably detect when there is no solution (you could let the algorithm run for a few seconds and if it doesn't finish, assume there is no solution) and that you cannot be sure you will get the solution with minimum number of elements chosen. Again, you could add some logic to make the algorithm keep going and trying to find a solution with less elements until certain stop conditions are met, but this will make it slower. However, if you are only interested in a solution that works and you have a LOT of numbers and the desired sum can be VERY big, this is probably better than the DP algorithm.

Another advantage of this approach is that it will also work for negative and rational numbers with no modifications, which is not true for the DP solution, because the DP solution involves using partial sums as array indexes, and indexes can only be natural numbers. You can of course use hashtables for example, but that will make the DP solution even slower.

IVlad
  • 43,099
  • 13
  • 111
  • 179
1

I don't know exactly what's this task is called, but it seems that it's kind of http://en.wikipedia.org/wiki/Knapsack_problem.

1

Heh, I'll play the "incomplete specification" card (nobody said that numbers couldn't appear more than once!) and reduce this to the "making change" problem. Sort your numbers in decreasing order, find the first one less than your desired sum, then subtract that from your sum (division and remainders could speed this up). Repeat until sum = 0 or no number less than the sum is found.

For completeness, you would need to keep track of the number of addends in each sum, and of course generate the additional sequences by keeping track of the first number you use, skipping that, and repeating the process with the additional numbers. This would solve the (7 + 2 + 1) over (6 + 4) problem.

TMN
  • 3,060
  • 21
  • 23
1

Repeating the answer of others: it is a Subset Sum problem. It could be efficiently solved by Dynamic Programing technique.

The following has not been mentioned yet: the problem is Pseudo-P (or NP-Complete in weak sense).

Existence of an algorithm (based on dynamic programming) polynomial in S (where S is the sum) and n (the number of elements) proves this claim.

Regards.

Slava
  • 827
  • 5
  • 13
0

Ok, I wrote a C++ program to solve the above problem. The algorithm is simple :-)

First of all arrange whatever array you have in descending order(I have hard-coded the array in descending form but you may apply any of the sorting algorithms ).

Next I took three stacks n, pos and sum. The first one stores the number for which a possible sum combination is to be found, the second holds the index of the array from where to start the search, the third stores the elements whose addition will give you the number you enter.

The function looks for the largest number in the array which is smaller than or equal to the number entered. If it is equal, it simply pushes the number onto the sum stack. If not, then it pushes the encountered array element to the sum stack(temporarily), and finds the difference between the number to search for and number encountered, and then it performs recursion.

Let me show an example:- to find 44 in {63,36,22,19,12,9,7,5,3,1}

first 36 will be pushed in sum(largest number less than 44) 44-36=8 will be pushed in n(next number to search for) 7 will be pushed in sum 8-7=1 will be pushed in n 1 will be pushed in sum

thus 44=36+7+1 :-)

#include <iostream>
#include<conio.h>
using namespace std;

int found=0;
void func(int n[],int pos[],int sum[],int arr[],int &topN,int &topP,int &topS)
{
int i=pos[topP],temp;
while(i<=9)
{
    if(arr[i]<=n[topN])
    {
        pos[topP]=i;
        topS++;
        sum[topS]=arr[i];
        temp=n[topN]-arr[i];
        if(temp==0)
            {
                found=1;
                break;
        }
topN++;
        n[topN]=temp;
        temp=pos[topP]+1;
        topP++;
        pos[topP]=temp;
        break;
    }
    i++;
}
if(i==10)
{
    topP=topP-1;
    topN=topN-1;
    pos[topP]+=1;
    topS=topS-1;
    if(topP!=-1)
    func(n,pos,sum,arr,topN,topP,topS);
}
else if(found!=1)
func(n,pos,sum,arr,topN,topP,topS);
}

main()
{
int x,n[100],pos[100],sum[100],arr[10]={63,36,22,19,12,9,7,5,3,1},topN=-1,topP=-1,topS=-1;
cout<<"Enter a number: ";
cin>>x;
topN=topN+1;
n[topN]=x;
topP=topP+1;
pos[topP]=0;
func(n,pos,sum,arr,topN,topP,topS);
if(found==0)
    cout<<"Not found any combination";
else{
cout<<"\n"<<sum[0];
for(int i=1;i<=topS;i++)
    cout<<" + "<<sum[i];
}
getch();
}

You can copy the code and paste it in your IDE, works fine :-)