Find out which combinations of numbers in a set add up to a given total

Viewed 65824

I've been tasked with helping some accountants solve a common problem they have - given a list of transactions and a total deposit, which transactions are part of the deposit? For example, say I have this list of numbers:

1.00
2.50
3.75
8.00

And I know that my total deposit is 10.50, I can easily see that it's made up of the 8.00 and 2.50 transaction. However, given a hundred transactions and a deposit in the millions, it quickly becomes much more difficult.

In testing a brute force solution (which takes way too long to be practical), I had two questions:

  1. With a list of about 60 numbers, it seems to find a dozen or more combinations for any total that's reasonable. I was expecting a single combination to satisfy my total, or maybe a few possibilities, but there always seem to be a ton of combinations. Is there a math principle that describes why this is? It seems that given a collection of random numbers of even a medium size, you can find a multiple combination that adds up to just about any total you want.

  2. I built a brute force solution for the problem, but it's clearly O(n!), and quickly grows out of control. Aside from the obvious shortcuts (exclude numbers larger than the total themselves), is there a way to shorten the time to calculate this?

Details on my current (super-slow) solution:

The list of detail amounts is sorted largest to smallest, and then the following process runs recursively:

  • Take the next item in the list and see if adding it to your running total makes your total match the target. If it does, set aside the current chain as a match. If it falls short of your target, add it to your running total, remove it from the list of detail amounts, and then call this process again

This way it excludes the larger numbers quickly, cutting the list down to only the numbers it needs to consider. However, it's still n! and larger lists never seem to finish, so I'm interested in any shortcuts I might be able to take to speed this up - I suspect that even cutting 1 number out of the list would cut the calculation time in half.

Thanks for your help!

10 Answers
#include <stdio.h>
#include <stdlib.h>

/* Takes at least 3 numbers as arguments.
 * First number is desired sum.
 * Find the subset of the rest that comes closest
 * to the desired sum without going over.
 */
static long *elements;
static int nelements;

/* A linked list of some elements, not necessarily all */
/* The list represents the optimal subset for elements in the range [index..nelements-1] */
struct status {
    long sum;                    /* sum of all the elements in the list */
    struct status *next;         /* points to next element in the list */
    int index;                   /* index into elements array of this element */
};

/*  find the subset of elements[startingat .. nelements-1]  whose sum is closest to but does not exceed desiredsum */
struct status *reportoptimalsubset(long desiredsum, int startingat) {
    struct status *sumcdr = NULL;
    struct status *sumlist = NULL;

    /* sum of zero elements or summing to zero */
    if (startingat == nelements || desiredsum == 0) {
        return NULL;
    }

    /* optimal sum using the current element */
    /* if current elements[startingat] too big, it won't fit, don't try it */
    if (elements[startingat] <= desiredsum) {
        sumlist = malloc(sizeof(struct status));
        sumlist->index = startingat;
        sumlist->next = reportoptimalsubset(desiredsum - elements[startingat], startingat + 1);
        sumlist->sum = elements[startingat] + (sumlist->next ? sumlist->next->sum : 0);
        if (sumlist->sum == desiredsum)
            return sumlist;
    }

    /* optimal sum not using current element */
    sumcdr = reportoptimalsubset(desiredsum, startingat + 1);

    if (!sumcdr) return sumlist;
    if (!sumlist) return sumcdr;

    return (sumcdr->sum < sumlist->sum) ?  sumlist : sumcdr;
}

int main(int argc, char **argv) {
  struct status *result = NULL;
  long desiredsum = strtol(argv[1], NULL, 10);

  nelements = argc - 2;
  elements = malloc(sizeof(long) * nelements);

  for (int i = 0; i < nelements; i++) {
      elements[i] = strtol(argv[i + 2], NULL , 10);
  }

  result = reportoptimalsubset(desiredsum, 0);
  if (result)
      printf("optimal subset = %ld\n", result->sum);

  while (result) {
      printf("%ld + ", elements[result->index]);
      result = result->next;
  }

  printf("\n");

}

Best to avoid use of floats and doubles when doing arithmetic and equality comparisons btw.

Related