Finding all possible combinations of numbers to reach a given sum

前端 未结 30 3035
一个人的身影
一个人的身影 2020-11-21 06:39

How would you go about testing all possible combinations of additions from a given set N of numbers so they add up to a given final number?

A brief exam

相关标签:
30条回答
  • 2020-11-21 07:18

    Very efficient algorithm using tables i wrote in c++ couple a years ago.

    If you set PRINT 1 it will print all combinations(but it wont be use the efficient method).

    Its so efficient that it calculate more than 10^14 combinations in less than 10ms.

    #include <stdio.h>
    #include <stdlib.h>
    //#include "CTime.h"
    
    #define SUM 300
    #define MAXNUMsSIZE 30
    
    #define PRINT 0
    
    
    long long CountAddToSum(int,int[],int,const int[],int);
    void printr(const int[], int);
    long long table1[SUM][MAXNUMsSIZE];
    
    int main()
    {
        int Nums[]={3,4,5,6,7,9,13,11,12,13,22,35,17,14,18,23,33,54};
        int sum=SUM;
        int size=sizeof(Nums)/sizeof(int);
        int i,j,a[]={0};
        long long N=0;
        //CTime timer1;
    
        for(i=0;i<SUM;++i) 
            for(j=0;j<MAXNUMsSIZE;++j) 
                table1[i][j]=-1;
    
        N = CountAddToSum(sum,Nums,size,a,0); //algorithm
        //timer1.Get_Passd();
    
        //printf("\nN=%lld time=%.1f ms\n", N,timer1.Get_Passd());
        printf("\nN=%lld \n", N);
        getchar();
        return 1;
    }
    
    long long CountAddToSum(int s, int arr[],int arrsize, const int r[],int rsize)
    {
        static int totalmem=0, maxmem=0;
        int i,*rnew;
        long long result1=0,result2=0;
    
        if(s<0) return 0;
        if (table1[s][arrsize]>0 && PRINT==0) return table1[s][arrsize];
        if(s==0)
        {
            if(PRINT) printr(r, rsize);
            return 1;
        }
        if(arrsize==0) return 0;
    
        //else
        rnew=(int*)malloc((rsize+1)*sizeof(int));
    
        for(i=0;i<rsize;++i) rnew[i]=r[i]; 
        rnew[rsize]=arr[arrsize-1];
    
        result1 =  CountAddToSum(s,arr,arrsize-1,rnew,rsize);
        result2 =  CountAddToSum(s-arr[arrsize-1],arr,arrsize,rnew,rsize+1);
        table1[s][arrsize]=result1+result2;
        free(rnew);
    
        return result1+result2;
    
    }
    
    void printr(const int r[], int rsize)
    {
        int lastr=r[0],count=0,i;
        for(i=0; i<rsize;++i) 
        {
            if(r[i]==lastr)
                count++;
            else
            {
                printf(" %d*%d ",count,lastr);
                lastr=r[i];
                count=1;
            }
        }
        if(r[i-1]==lastr) printf(" %d*%d ",count,lastr);
    
        printf("\n");
    
    }
    
    0 讨论(0)
  • 2020-11-21 07:20

    There are a lot of solutions so far, but all are of the form generate then filter. Which means that they potentially spend a lot of time working on recursive paths that do not lead to a solution.

    Here is a solution that is O(size_of_array * (number_of_sums + number_of_solutions)). In other words it uses dynamic programming to avoid enumerating possible solutions that will never match.

    For giggles and grins I made this work with numbers that are both positive and negative, and made it an iterator. It will work for Python 2.3+.

    def subset_sum_iter(array, target):
        sign = 1
        array = sorted(array)
        if target < 0:
            array = reversed(array)
            sign = -1
    
        last_index = {0: [-1]}
        for i in range(len(array)):
            for s in list(last_index.keys()):
                new_s = s + array[i]
                if 0 < (new_s - target) * sign:
                    pass # Cannot lead to target
                elif new_s in last_index:
                    last_index[new_s].append(i)
                else:
                    last_index[new_s] = [i]
    
        # Now yield up the answers.
        def recur (new_target, max_i):
            for i in last_index[new_target]:
                if i == -1:
                    yield [] # Empty sum.
                elif max_i <= i:
                    break # Not our solution.
                else:
                    for answer in recur(new_target - array[i], i):
                        answer.append(array[i])
                        yield answer
    
        for answer in recur(target, len(array)):
            yield answer
    

    And here is an example of it being used with an array and target where the filtering approach used in other solutions would effectively never finish.

    def is_prime (n):
        for i in range(2, n):
            if 0 == n%i:
                return False
            elif n < i*i:
                return True
        if n == 2:
            return True
        else:
            return False
    
    def primes (limit):
        n = 2
        while True:
            if is_prime(n):
                yield(n)
            n = n+1
            if limit < n:
                break
    
    for answer in subset_sum_iter(primes(1000), 76000):
        print(answer)
    

    This prints all 522 answers in under 2 seconds. The previous approaches would be lucky to find any answers in the current lifetime of the universe. (The full space has 2^168 = 3.74144419156711e+50 possible combinations to run through. That...takes a while.)

    0 讨论(0)
  • 2020-11-21 07:21

    The solution of this problem has been given a million times on the Internet. The problem is called The coin changing problem. One can find solutions at http://rosettacode.org/wiki/Count_the_coins and mathematical model of it at http://jaqm.ro/issues/volume-5,issue-2/pdfs/patterson_harmel.pdf (or Google coin change problem).

    By the way, the Scala solution by Tsagadai, is interesting. This example produces either 1 or 0. As a side effect, it lists on the console all possible solutions. It displays the solution, but fails making it usable in any way.

    To be as useful as possible, the code should return a List[List[Int]]in order to allow getting the number of solution (length of the list of lists), the "best" solution (the shortest list), or all the possible solutions.

    Here is an example. It is very inefficient, but it is easy to understand.

    object Sum extends App {
    
      def sumCombinations(total: Int, numbers: List[Int]): List[List[Int]] = {
    
        def add(x: (Int, List[List[Int]]), y: (Int, List[List[Int]])): (Int, List[List[Int]]) = {
          (x._1 + y._1, x._2 ::: y._2)
        }
    
        def sumCombinations(resultAcc: List[List[Int]], sumAcc: List[Int], total: Int, numbers: List[Int]): (Int, List[List[Int]]) = {
          if (numbers.isEmpty || total < 0) {
            (0, resultAcc)
          } else if (total == 0) {
            (1, sumAcc :: resultAcc)
          } else {
            add(sumCombinations(resultAcc, sumAcc, total, numbers.tail), sumCombinations(resultAcc, numbers.head :: sumAcc, total - numbers.head, numbers))
          }
        }
    
        sumCombinations(Nil, Nil, total, numbers.sortWith(_ > _))._2
      }
    
      println(sumCombinations(15, List(1, 2, 5, 10)) mkString "\n")
    }
    

    When run, it displays:

    List(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
    List(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2)
    List(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2)
    List(1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2)
    List(1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2)
    List(1, 1, 1, 1, 1, 2, 2, 2, 2, 2)
    List(1, 1, 1, 2, 2, 2, 2, 2, 2)
    List(1, 2, 2, 2, 2, 2, 2, 2)
    List(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5)
    List(1, 1, 1, 1, 1, 1, 1, 1, 2, 5)
    List(1, 1, 1, 1, 1, 1, 2, 2, 5)
    List(1, 1, 1, 1, 2, 2, 2, 5)
    List(1, 1, 2, 2, 2, 2, 5)
    List(2, 2, 2, 2, 2, 5)
    List(1, 1, 1, 1, 1, 5, 5)
    List(1, 1, 1, 2, 5, 5)
    List(1, 2, 2, 5, 5)
    List(5, 5, 5)
    List(1, 1, 1, 1, 1, 10)
    List(1, 1, 1, 2, 10)
    List(1, 2, 2, 10)
    List(5, 10)
    

    The sumCombinations() function may be used by itself, and the result may be further analyzed to display the "best" solution (the shortest list), or the number of solutions (the number of lists).

    Note that even like this, the requirements may not be fully satisfied. It might happen that the order of each list in the solution be significant. In such a case, each list would have to be duplicated as many time as there are combination of its elements. Or we might be interested only in the combinations that are different.

    For example, we might consider that List(5, 10) should give two combinations: List(5, 10) and List(10, 5). For List(5, 5, 5) it could give three combinations or one only, depending on the requirements. For integers, the three permutations are equivalent, but if we are dealing with coins, like in the "coin changing problem", they are not.

    Also not stated in the requirements is the question of whether each number (or coin) may be used only once or many times. We could (and we should!) generalize the problem to a list of lists of occurrences of each number. This translates in real life into "what are the possible ways to make an certain amount of money with a set of coins (and not a set of coin values)". The original problem is just a particular case of this one, where we have as many occurrences of each coin as needed to make the total amount with each single coin value.

    0 讨论(0)
  • 2020-11-21 07:21

    Another python solution would be to use the itertools.combinations module as follows:

    #!/usr/local/bin/python
    
    from itertools import combinations
    
    def find_sum_in_list(numbers, target):
        results = []
        for x in range(len(numbers)):
            results.extend(
                [   
                    combo for combo in combinations(numbers ,x)  
                        if sum(combo) == target
                ]   
            )   
    
        print results
    
    if __name__ == "__main__":
        find_sum_in_list([3,9,8,4,5,7,10], 15)
    

    Output: [(8, 7), (5, 10), (3, 8, 4), (3, 5, 7)]

    0 讨论(0)
  • 2020-11-21 07:22

    @KeithBeller's answer with slightly changed variable names and some comments.

        public static void Main(string[] args)
        {
            List<int> input = new List<int>() { 3, 9, 8, 4, 5, 7, 10 };
            int targetSum = 15;
            SumUp(input, targetSum);
        }
    
        public static void SumUp(List<int> input, int targetSum)
        {
            SumUpRecursive(input, targetSum, new List<int>());
        }
    
        private static void SumUpRecursive(List<int> remaining, int targetSum, List<int> listToSum)
        {
            // Sum up partial
            int sum = 0;
            foreach (int x in listToSum)
                sum += x;
    
            //Check sum matched
            if (sum == targetSum)
                Console.WriteLine("sum(" + string.Join(",", listToSum.ToArray()) + ")=" + targetSum);
    
            //Check sum passed
            if (sum >= targetSum)
                return;
    
            //Iterate each input character
            for (int i = 0; i < remaining.Count; i++)
            {
                //Build list of remaining items to iterate
                List<int> newRemaining = new List<int>();
                for (int j = i + 1; j < remaining.Count; j++)
                    newRemaining.Add(remaining[j]);
    
                //Update partial list
                List<int> newListToSum = new List<int>(listToSum);
                int currentItem = remaining[i];
                newListToSum.Add(currentItem);
                SumUpRecursive(newRemaining, targetSum, newListToSum);
            }
        }'
    
    0 讨论(0)
  • 2020-11-21 07:23

    This problem can be solved with a recursive combinations of all possible sums filtering out those that reach the target. Here is the algorithm in Python:

    def subset_sum(numbers, target, partial=[]):
        s = sum(partial)
    
        # check if the partial sum is equals to target
        if s == target: 
            print "sum(%s)=%s" % (partial, target)
        if s >= target:
            return  # if we reach the number why bother to continue
    
        for i in range(len(numbers)):
            n = numbers[i]
            remaining = numbers[i+1:]
            subset_sum(remaining, target, partial + [n]) 
    
    
    if __name__ == "__main__":
        subset_sum([3,9,8,4,5,7,10],15)
    
        #Outputs:
        #sum([3, 8, 4])=15
        #sum([3, 5, 7])=15
        #sum([8, 7])=15
        #sum([5, 10])=15
    

    This type of algorithms are very well explained in the following Standford's Abstract Programming lecture - this video is very recommendable to understand how recursion works to generate permutations of solutions.

    Edit

    The above as a generator function, making it a bit more useful. Requires Python 3.3+ because of yield from.

    def subset_sum(numbers, target, partial=[], partial_sum=0):
        if partial_sum == target:
            yield partial
        if partial_sum >= target:
            return
        for i, n in enumerate(numbers):
            remaining = numbers[i + 1:]
            yield from subset_sum(remaining, target, partial + [n], partial_sum + n)
    

    Here is the Java version of the same algorithm:

    package tmp;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    
    class SumSet {
        static void sum_up_recursive(ArrayList<Integer> numbers, int target, ArrayList<Integer> partial) {
           int s = 0;
           for (int x: partial) s += x;
           if (s == target)
                System.out.println("sum("+Arrays.toString(partial.toArray())+")="+target);
           if (s >= target)
                return;
           for(int i=0;i<numbers.size();i++) {
                 ArrayList<Integer> remaining = new ArrayList<Integer>();
                 int n = numbers.get(i);
                 for (int j=i+1; j<numbers.size();j++) remaining.add(numbers.get(j));
                 ArrayList<Integer> partial_rec = new ArrayList<Integer>(partial);
                 partial_rec.add(n);
                 sum_up_recursive(remaining,target,partial_rec);
           }
        }
        static void sum_up(ArrayList<Integer> numbers, int target) {
            sum_up_recursive(numbers,target,new ArrayList<Integer>());
        }
        public static void main(String args[]) {
            Integer[] numbers = {3,9,8,4,5,7,10};
            int target = 15;
            sum_up(new ArrayList<Integer>(Arrays.asList(numbers)),target);
        }
    }
    

    It is exactly the same heuristic. My Java is a bit rusty but I think is easy to understand.

    C# conversion of Java solution: (by @JeremyThompson)

    public static void Main(string[] args)
    {
        List<int> numbers = new List<int>() { 3, 9, 8, 4, 5, 7, 10 };
        int target = 15;
        sum_up(numbers, target);
    }
    
    private static void sum_up(List<int> numbers, int target)
    {
        sum_up_recursive(numbers, target, new List<int>());
    }
    
    private static void sum_up_recursive(List<int> numbers, int target, List<int> partial)
    {
        int s = 0;
        foreach (int x in partial) s += x;
    
        if (s == target)
            Console.WriteLine("sum(" + string.Join(",", partial.ToArray()) + ")=" + target);
    
        if (s >= target)
            return;
    
        for (int i = 0; i < numbers.Count; i++)
        {
            List<int> remaining = new List<int>();
            int n = numbers[i];
            for (int j = i + 1; j < numbers.Count; j++) remaining.Add(numbers[j]);
    
            List<int> partial_rec = new List<int>(partial);
            partial_rec.Add(n);
            sum_up_recursive(remaining, target, partial_rec);
        }
    }
    

    Ruby solution: (by @emaillenin)

    def subset_sum(numbers, target, partial=[])
      s = partial.inject 0, :+
    # check if the partial sum is equals to target
    
      puts "sum(#{partial})=#{target}" if s == target
    
      return if s >= target # if we reach the number why bother to continue
    
      (0..(numbers.length - 1)).each do |i|
        n = numbers[i]
        remaining = numbers.drop(i+1)
        subset_sum(remaining, target, partial + [n])
      end
    end
    
    subset_sum([3,9,8,4,5,7,10],15)
    

    Edit: complexity discussion

    As others mention this is an NP-hard problem. It can be solved in exponential time O(2^n), for instance for n=10 there will be 1024 possible solutions. If the targets you are trying to reach are in a low range then this algorithm works. So for instance:

    subset_sum([1,2,3,4,5,6,7,8,9,10],100000) generates 1024 branches because the target never gets to filter out possible solutions.

    On the other hand subset_sum([1,2,3,4,5,6,7,8,9,10],10) generates only 175 branches, because the target to reach 10 gets to filter out many combinations.

    If N and Target are big numbers one should move into an approximate version of the solution.

    0 讨论(0)
提交回复
热议问题