代码之家  ›  专栏  ›  技术社区  ›  Burhan

PHP:需要选择一个精确和的组合[重复]

  •  0
  • Burhan  · 技术社区  · 6 年前

    你将如何测试从一组给定的数字中所有可能的加法组合,使它们相加成一个给定的最终数字?

    • 要添加的数字集:{1,5,22,15,0,…}
    • 期望结果:12345
    0 回复  |  直到 5 年前
        1
  •  226
  •   user2357112    8 年前

    这个问题可以通过所有可能的和的递归组合来解决,过滤掉那些达到目标的和。以下是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
    

    Standford's Abstract Programming lecture -这段视频非常有助于理解递归如何产生解的排列。

    编辑

    以上作为一个生成器函数,使其更有用一些。需要Python3.3+,因为 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)
    

    下面是相同算法的Java版本:

    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);
        }
    }
    

    这完全是同样的启发。我的Java有点生疏,但我认为很容易理解。

    (作者@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);
        }
    }
    

    (作者@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)
    

    编辑:复杂性讨论

    NP-hard problem . 它可以在指数时间O(2^n)内求解,例如n=10,将有1024个可能的解。如果你试图达到的目标是在一个较低的范围内,那么这个算法是可行的。例如:

    subset_sum([1,2,3,4,5,6,7,8,9,10],100000) 生成1024个分支,因为目标永远无法筛选出可能的解决方案。

    另一方面 subset_sum([1,2,3,4,5,6,7,8,9,10],10) 只生成175个分支,因为要到达的目标 10

    如果 N Target 一个大数字应该转化为近似解的版本。

        2
  •  32
  •   Rodrigue    7 年前

    这个问题的解决方案已经在互联网上被给出了无数次。这个问题叫做 . 你可以在 http://rosettacode.org/wiki/Count_the_coins http://jaqm.ro/issues/volume-5,issue-2/pdfs/patterson_harmel.pdf (或谷歌 硬币兑换问题 ).

    为了尽可能有用,代码应该返回 List[List[Int]] 为了获得解决方案的数量(列表的长度)、“最佳”解决方案(最短列表)或所有可能的解决方案。

    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")
    }
    

    运行时,显示:

    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)
    

    这个 sumCombinations() 函数可以自己使用,并且可以进一步分析结果以显示“最佳”解决方案(最短列表)或解决方案数(列表数)。

    请注意,即使这样,需求也可能无法完全满足。解决方案中每个列表的顺序可能很重要。在这种情况下,每个列表的重复次数必须与其元素的组合次数相同。或者我们可能只对不同的组合感兴趣。

    例如,我们可以考虑 List(5, 10) 列表(5,10) List(10, 5) List(5, 5, 5) 根据需求,它可以提供三种组合,也可以只提供一种。对于整数,这三个排列是等价的,但是如果我们处理硬币,就像在“硬币更换问题”中那样,它们就不是。

    要求中也没有说明每个数字(或硬币)是否只能使用一次或多次的问题。我们可以(也应该!)将问题归纳为每个数字出现的列表。这在现实生活中转化为“用一组硬币(而不是一组硬币的价值)来赚一定数量的钱的可能方法是什么”。最初的问题只是这个问题的一个特例,在这里,我们有尽可能多的每一枚硬币的出现,以使总金额与每一枚硬币的价值。

        3
  •  31
  •   smac89    7 年前

    Haskell :

    filter ((==) 12345 . sum) $ subsequences [1,5,22,15,0,..]
    

    以及 J :

    (]#~12345=+/@>)(]<@#~[:#:@i.2^#)1 5 22 15 0 ...
    

    还有其他的解决方案,但这是最直接的。

    你需要帮助其中一个,还是寻找不同的方法?

        4
  •  28
  •   rbarilani    9 年前

    function subsetSum(numbers, target, partial) {
      var s, n, remaining;
    
      partial = partial || [];
    
      // sum partial
      s = partial.reduce(function (a, b) {
        return a + b;
      }, 0);
    
      // check if the partial sum is equals to target
      if (s === target) {
        console.log("%s=%s", partial.join("+"), target)
      }
    
      if (s >= target) {
        return;  // if we reach the number why bother to continue
      }
    
      for (var i = 0; i < numbers.length; i++) {
        n = numbers[i];
        remaining = numbers.slice(i + 1);
        subsetSum(remaining, target, partial.concat([n]));
      }
    }
    
    subsetSum([3,9,8,4,5,7,10],15);
    
    // output:
    // 3+8+4=15
    // 3+5+7=15
    // 8+7=15
    // 5+10=15
        5
  •  11
  •   smac89    7 年前

    C版@msalvadores代码应答

    void Main()
    {
        int[] numbers = {3,9,8,4,5,7,10};
        int target = 15;
        sum_up(new List<int>(numbers.ToList()),target);
    }
    
    static void sum_up_recursive(List<int> numbers, int target, List<int> part)
    {
       int s = 0;
       foreach (int x in part)
       {
           s += x;
       }
       if (s == target)
       {
            Console.WriteLine("sum(" + string.Join(",", part.Select(n => n.ToString()).ToArray()) + ")=" + target);
       }
       if (s >= target)
       {
            return;
       }
       for (int i = 0;i < numbers.Count;i++)
       {
             var remaining = new List<int>();
             int n = numbers[i];
             for (int j = i + 1; j < numbers.Count;j++)
             {
                 remaining.Add(numbers[j]);
             }
             var part_rec = new List<int>(part);
             part_rec.Add(n);
             sum_up_recursive(remaining,target,part_rec);
       }
    }
    static void sum_up(List<int> numbers, int target)
    {
        sum_up_recursive(numbers,target,new List<int>());
    }
    
        6
  •  11
  •   smac89    7 年前

    C++版本的同一算法

    #include <iostream>
    #include <list>
    void subset_sum_recursive(std::list<int> numbers, int target, std::list<int> partial)
    {
            int s = 0;
            for (std::list<int>::const_iterator cit = partial.begin(); cit != partial.end(); cit++)
            {
                s += *cit;
            }
            if(s == target)
            {
                    std::cout << "sum([";
    
                    for (std::list<int>::const_iterator cit = partial.begin(); cit != partial.end(); cit++)
                    {
                        std::cout << *cit << ",";
                    }
                    std::cout << "])=" << target << std::endl;
            }
            if(s >= target)
                return;
            int n;
            for (std::list<int>::const_iterator ai = numbers.begin(); ai != numbers.end(); ai++)
            {
                n = *ai;
                std::list<int> remaining;
                for(std::list<int>::const_iterator aj = ai; aj != numbers.end(); aj++)
                {
                    if(aj == ai)continue;
                    remaining.push_back(*aj);
                }
                std::list<int> partial_rec=partial;
                partial_rec.push_back(n);
                subset_sum_recursive(remaining,target,partial_rec);
    
            }
    }
    
    void subset_sum(std::list<int> numbers,int target)
    {
        subset_sum_recursive(numbers,target,std::list<int>());
    }
    int main()
    {
        std::list<int> a;
        a.push_back (3); a.push_back (9); a.push_back (8);
        a.push_back (4);
        a.push_back (5);
        a.push_back (7);
        a.push_back (10);
        int n = 15;
        //std::cin >> n;
        subset_sum(a, n);
        return 0;
    }
    
        7
  •  4
  •   smac89    7 年前

    我想我会用这个问题的答案,但我不能,所以这是我的答案。它使用的是 Structure and Interpretation of Computer Programs . 我认为这是一个更好的递归解决方案,应该让纯粹主义者更高兴。

    搜索结果组合 疯狂是对递归的原始列表进行排序和唯一化,以防止重复。

    def findSumCombinations(target: Int, numbers: List[Int]): Int = {
      cc(target, numbers.distinct.sortWith(_ < _), List())
    }
    
    def cc(target: Int, numbers: List[Int], solution: List[Int]): Int = {
      if (target == 0) {println(solution); 1 }
      else if (target < 0 || numbers.length == 0) 0
      else 
        cc(target, numbers.tail, solution) 
        + cc(target - numbers.head, numbers, numbers.head :: solution)
    }
    

     > findSumCombinations(12345, List(1,5,22,15,0,..))
     * Prints a whole heap of lists that will sum to the target *
    
        8
  •  4
  •   smac89    7 年前
    Thank you.. ephemient
    

    我已经把上面的逻辑从python转换成php。。

    <?php
    $data = array(array(2,3,5,10,15),array(4,6,23,15,12),array(23,34,12,1,5));
    $maxsum = 25;
    
    print_r(bestsum($data,$maxsum));  //function call
    
    function bestsum($data,$maxsum)
    {
    $res = array_fill(0, $maxsum + 1, '0');
    $res[0] = array();              //base case
    foreach($data as $group)
    {
     $new_res = $res;               //copy res
    
      foreach($group as $ele)
      {
        for($i=0;$i<($maxsum-$ele+1);$i++)
        {   
            if($res[$i] != 0)
            {
                $ele_index = $i+$ele;
                $new_res[$ele_index] = $res[$i];
                $new_res[$ele_index][] = $ele;
            }
        }
      }
    
      $res = $new_res;
    }
    
     for($i=$maxsum;$i>0;$i--)
      {
        if($res[$i]!=0)
        {
            return $res[$i];
            break;
        }
      }
    return array();
    }
    ?>
    
        9
  •  4
  •   Sebastián Palma    7 年前

    另一个python解决方案是使用 itertools.combinations 模块如下:

    #!/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)
    

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

        10
  •  3
  •   smac89    7 年前

    这里是一个Java版本,非常适合小N和非常大的目标和,当复杂性 O(t*N) (动态解)大于指数算法。我的版本使用一个在中间的攻击,以及一点点移位,以减少从经典天真的复杂性。 O(n*2^n) O(2^(n/2)) .

    如果要对32到64个元素之间的集使用此选项,则应更改 int 它表示步骤函数中的当前子集 long

    import java.util.ArrayList;
    import java.util.List;
    
    public class SubsetSumMiddleAttack {
        static final int target = 100000000;
        static final int[] set = new int[]{ ... };
    
        static List<Subset> evens = new ArrayList<>();
        static List<Subset> odds = new ArrayList<>();
    
        static int[][] split(int[] superSet) {
            int[][] ret = new int[2][superSet.length / 2]; 
    
            for (int i = 0; i < superSet.length; i++) ret[i % 2][i / 2] = superSet[i];
    
            return ret;
        }
    
        static void step(int[] superSet, List<Subset> accumulator, int subset, int sum, int counter) {
            accumulator.add(new Subset(subset, sum));
            if (counter != superSet.length) {
                step(superSet, accumulator, subset + (1 << counter), sum + superSet[counter], counter + 1);
                step(superSet, accumulator, subset, sum, counter + 1);
            }
        }
    
        static void printSubset(Subset e, Subset o) {
            String ret = "";
            for (int i = 0; i < 32; i++) {
                if (i % 2 == 0) {
                    if ((1 & (e.subset >> (i / 2))) == 1) ret += " + " + set[i];
                }
                else {
                    if ((1 & (o.subset >> (i / 2))) == 1) ret += " + " + set[i];
                }
            }
            if (ret.startsWith(" ")) ret = ret.substring(3) + " = " + (e.sum + o.sum);
            System.out.println(ret);
        }
    
        public static void main(String[] args) {
            int[][] superSets = split(set);
    
            step(superSets[0], evens, 0,0,0);
            step(superSets[1], odds, 0,0,0);
    
            for (Subset e : evens) {
                for (Subset o : odds) {
                    if (e.sum + o.sum == target) printSubset(e, o);
                }
            }
        }
    }
    
    class Subset {
        int subset;
        int sum;
    
        Subset(int subset, int sum) {
            this.subset = subset;
            this.sum = sum;
        }
    }
    
        11
  •  3
  •   smac89    7 年前

    这类似于硬币兑换问题

    public class CoinCount 
    {   
    public static void main(String[] args)
    {
        int[] coins={1,4,6,2,3,5};
        int count=0;
    
        for (int i=0;i<coins.length;i++)
        {
            count=count+Count(9,coins,i,0);
        }
        System.out.println(count);
    }
    
    public static int Count(int Sum,int[] coins,int index,int curSum)
    {
        int count=0;
    
        if (index>=coins.length)
            return 0;
    
        int sumNow=curSum+coins[index];
        if (sumNow>Sum)
            return 0;
        if (sumNow==Sum)
            return 1;
    
        for (int i= index+1;i<coins.length;i++)
            count+=Count(Sum,coins,i,sumNow);
    
        return count;       
    }
    }
    
        12
  •  3
  •   Mark    7 年前

    这是R中的一个解决方案

    subset_sum = function(numbers,target,partial=0){
      if(any(is.na(partial))) return()
      s = sum(partial)
      if(s == target) print(sprintf("sum(%s)=%s",paste(partial[-1],collapse="+"),target))
      if(s > target) return()
      for( i in seq_along(numbers)){
        n = numbers[i]
        remaining = numbers[(i+1):length(numbers)]
        subset_sum(remaining,target,c(partial,n))
      }
    }
    
        13
  •  2
  •   smac89    7 年前

    如果设置PRINT 1,它将打印所有组合(但不会使用有效的方法)。

    #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");
    
    }
    
        14
  •  2
  •   smac89    7 年前

    这里有一个更好的版本,具有更好的输出格式和C++ 11个特性:

    void subset_sum_rec(std::vector<int> & nums, const int & target, std::vector<int> & partialNums) 
    {
        int currentSum = std::accumulate(partialNums.begin(), partialNums.end(), 0);
        if (currentSum > target)
            return;
        if (currentSum == target) 
        {
            std::cout << "sum([";
            for (auto it = partialNums.begin(); it != std::prev(partialNums.end()); ++it)
                cout << *it << ",";
            cout << *std::prev(partialNums.end());
            std::cout << "])=" << target << std::endl;
        }
        for (auto it = nums.begin(); it != nums.end(); ++it) 
        {
            std::vector<int> remaining;
            for (auto it2 = std::next(it); it2 != nums.end(); ++it2)
                remaining.push_back(*it2);
    
            std::vector<int> partial = partialNums;
            partial.push_back(*it);
            subset_sum_rec(remaining, target, partial);
        }
    }
    
        15
  •  2
  •   Bernat    6 年前

    爪哇 非递归版本,只需不断添加元素并在可能的值之间重新分配它们。 0

    import java.util.*;
    
    public class TestCombinations {
    
        public static void main(String[] args) {
            ArrayList<Integer> numbers = new ArrayList<>(Arrays.asList(0, 1, 2, 2, 5, 10, 20));
            LinkedHashSet<Integer> targets = new LinkedHashSet<Integer>() {{
                add(4);
                add(10);
                add(25);
            }};
    
            System.out.println("## each element can appear as many times as needed");
            for (Integer target: targets) {
                Combinations combinations = new Combinations(numbers, target, true);
                combinations.calculateCombinations();
                for (String solution: combinations.getCombinations()) {
                    System.out.println(solution);
                }
            }
    
            System.out.println("## each element can appear only once");
            for (Integer target: targets) {
                Combinations combinations = new Combinations(numbers, target, false);
                combinations.calculateCombinations();
                for (String solution: combinations.getCombinations()) {
                    System.out.println(solution);
                }
            }
        }
    
        public static class Combinations {
            private boolean allowRepetitions;
            private int[] repetitions;
            private ArrayList<Integer> numbers;
            private Integer target;
            private Integer sum;
            private boolean hasNext;
            private Set<String> combinations;
    
            /**
             * Constructor.
             *
             * @param numbers Numbers that can be used to calculate the sum.
             * @param target  Target value for sum.
             */
            public Combinations(ArrayList<Integer> numbers, Integer target) {
                this(numbers, target, true);
            }
    
            /**
             * Constructor.
             *
             * @param numbers Numbers that can be used to calculate the sum.
             * @param target  Target value for sum.
             */
            public Combinations(ArrayList<Integer> numbers, Integer target, boolean allowRepetitions) {
                this.allowRepetitions = allowRepetitions;
                if (this.allowRepetitions) {
                    Set<Integer> numbersSet = new HashSet<>(numbers);
                    this.numbers = new ArrayList<>(numbersSet);
                } else {
                    this.numbers = numbers;
                }
                this.numbers.removeAll(Arrays.asList(0));
                Collections.sort(this.numbers);
    
                this.target = target;
                this.repetitions = new int[this.numbers.size()];
                this.combinations = new LinkedHashSet<>();
    
                this.sum = 0;
                if (this.repetitions.length > 0)
                    this.hasNext = true;
                else
                    this.hasNext = false;
            }
    
            /**
             * Calculate and return the sum of the current combination.
             *
             * @return The sum.
             */
            private Integer calculateSum() {
                this.sum = 0;
                for (int i = 0; i < repetitions.length; ++i) {
                    this.sum += repetitions[i] * numbers.get(i);
                }
                return this.sum;
            }
    
            /**
             * Redistribute picks when only one of each number is allowed in the sum.
             */
            private void redistribute() {
                for (int i = 1; i < this.repetitions.length; ++i) {
                    if (this.repetitions[i - 1] > 1) {
                        this.repetitions[i - 1] = 0;
                        this.repetitions[i] += 1;
                    }
                }
                if (this.repetitions[this.repetitions.length - 1] > 1)
                    this.repetitions[this.repetitions.length - 1] = 0;
            }
    
            /**
             * Get the sum of the next combination. When 0 is returned, there's no other combinations to check.
             *
             * @return The sum.
             */
            private Integer next() {
                if (this.hasNext && this.repetitions.length > 0) {
                    this.repetitions[0] += 1;
                    if (!this.allowRepetitions)
                        this.redistribute();
                    this.calculateSum();
    
                    for (int i = 0; i < this.repetitions.length && this.sum != 0; ++i) {
                        if (this.sum > this.target) {
                            this.repetitions[i] = 0;
                            if (i + 1 < this.repetitions.length) {
                                this.repetitions[i + 1] += 1;
                                if (!this.allowRepetitions)
                                    this.redistribute();
                            }
                            this.calculateSum();
                        }
                    }
    
                    if (this.sum.compareTo(0) == 0)
                        this.hasNext = false;
                }
                return this.sum;
            }
    
            /**
             * Calculate all combinations whose sum equals target.
             */
            public void calculateCombinations() {
                while (this.hasNext) {
                    if (this.next().compareTo(target) == 0)
                        this.combinations.add(this.toString());
                }
            }
    
            /**
             * Return all combinations whose sum equals target.
             *
             * @return Combinations as a set of strings.
             */
            public Set<String> getCombinations() {
                return this.combinations;
            }
    
            @Override
            public String toString() {
                StringBuilder stringBuilder = new StringBuilder("" + sum + ": ");
                for (int i = 0; i < repetitions.length; ++i) {
                    for (int j = 0; j < repetitions[i]; ++j) {
                        stringBuilder.append(numbers.get(i) + " ");
                    }
                }
                return stringBuilder.toString();
            }
        }
    }
    

    样本输入:

    numbers: 0, 1, 2, 2, 5, 10, 20
    targets: 4, 10, 25
    

    样本输出:

    ## each element can appear as many times as needed
    4: 1 1 1 1 
    4: 1 1 2 
    4: 2 2 
    10: 1 1 1 1 1 1 1 1 1 1 
    10: 1 1 1 1 1 1 1 1 2 
    10: 1 1 1 1 1 1 2 2 
    10: 1 1 1 1 2 2 2 
    10: 1 1 2 2 2 2 
    10: 2 2 2 2 2 
    10: 1 1 1 1 1 5 
    10: 1 1 1 2 5 
    10: 1 2 2 5 
    10: 5 5 
    10: 10 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 
    25: 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 
    25: 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 
    25: 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 
    25: 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 
    25: 1 1 1 2 2 2 2 2 2 2 2 2 2 2 
    25: 1 2 2 2 2 2 2 2 2 2 2 2 2 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 5 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 5 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 5 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 5 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 5 
    25: 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 5 
    25: 1 1 1 1 1 1 1 1 2 2 2 2 2 2 5 
    25: 1 1 1 1 1 1 2 2 2 2 2 2 2 5 
    25: 1 1 1 1 2 2 2 2 2 2 2 2 5 
    25: 1 1 2 2 2 2 2 2 2 2 2 5 
    25: 2 2 2 2 2 2 2 2 2 2 5 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 5 5 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 2 5 5 
    25: 1 1 1 1 1 1 1 1 1 1 1 2 2 5 5 
    25: 1 1 1 1 1 1 1 1 1 2 2 2 5 5 
    25: 1 1 1 1 1 1 1 2 2 2 2 5 5 
    25: 1 1 1 1 1 2 2 2 2 2 5 5 
    25: 1 1 1 2 2 2 2 2 2 5 5 
    25: 1 2 2 2 2 2 2 2 5 5 
    25: 1 1 1 1 1 1 1 1 1 1 5 5 5 
    25: 1 1 1 1 1 1 1 1 2 5 5 5 
    25: 1 1 1 1 1 1 2 2 5 5 5 
    25: 1 1 1 1 2 2 2 5 5 5 
    25: 1 1 2 2 2 2 5 5 5 
    25: 2 2 2 2 2 5 5 5 
    25: 1 1 1 1 1 5 5 5 5 
    25: 1 1 1 2 5 5 5 5 
    25: 1 2 2 5 5 5 5 
    25: 5 5 5 5 5 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 10 
    25: 1 1 1 1 1 1 1 1 1 1 1 1 1 2 10 
    25: 1 1 1 1 1 1 1 1 1 1 1 2 2 10 
    25: 1 1 1 1 1 1 1 1 1 2 2 2 10 
    25: 1 1 1 1 1 1 1 2 2 2 2 10 
    25: 1 1 1 1 1 2 2 2 2 2 10 
    25: 1 1 1 2 2 2 2 2 2 10 
    25: 1 2 2 2 2 2 2 2 10 
    25: 1 1 1 1 1 1 1 1 1 1 5 10 
    25: 1 1 1 1 1 1 1 1 2 5 10 
    25: 1 1 1 1 1 1 2 2 5 10 
    25: 1 1 1 1 2 2 2 5 10 
    25: 1 1 2 2 2 2 5 10 
    25: 2 2 2 2 2 5 10 
    25: 1 1 1 1 1 5 5 10 
    25: 1 1 1 2 5 5 10 
    25: 1 2 2 5 5 10 
    25: 5 5 5 10 
    25: 1 1 1 1 1 10 10 
    25: 1 1 1 2 10 10 
    25: 1 2 2 10 10 
    25: 5 10 10 
    25: 1 1 1 1 1 20 
    25: 1 1 1 2 20 
    25: 1 2 2 20 
    25: 5 20 
    ## each element can appear only once
    4: 2 2 
    10: 1 2 2 5 
    10: 10 
    25: 1 2 2 20 
    25: 5 20
    
        16
  •  1
  •   Mario S    12 年前

    使用excel查找组合(相当容易)。 (您的计算机不能太慢)

    1. Go to this site
    2. 下载“Sum to Target”excel文件。

      按照网站页面上的说明操作。

    希望这有帮助。

        17
  •  1
  •   RolandasR    8 年前

    protocol _IntType { }
    extension Int: _IntType {}
    
    
    extension Array where Element: _IntType {
    
        func subsets(to: Int) -> [[Element]]? {
    
            func sum_up_recursive(_ numbers: [Element], _ target: Int, _ partial: [Element], _ solution: inout [[Element]]) {
    
                var sum: Int = 0
                for x in partial {
                    sum += x as! Int
                }
    
                if sum == target {
                    solution.append(partial)
                }
    
                guard sum < target else {
                    return
                }
    
                for i in stride(from: 0, to: numbers.count, by: 1) {
    
                    var remaining = [Element]()
    
                    for j in stride(from: i + 1, to: numbers.count, by: 1) {
                        remaining.append(numbers[j])
                    }
    
                    var partial_rec = [Element](partial)
                    partial_rec.append(numbers[i])
    
                    sum_up_recursive(remaining, target, partial_rec, &solution)
                }
            }
    
            var solutions = [[Element]]()
            sum_up_recursive(self, to, [Element](), &solutions)
    
            return solutions.count > 0 ? solutions : nil
        }
    
    }
    

    用法:

    let numbers = [3, 9, 8, 4, 5, 7, 10]
    
    if let solution = numbers.subsets(to: 15) {
        print(solution) // output: [[3, 8, 4], [3, 5, 7], [8, 7], [5, 10]]
    } else {
        print("not possible")
    }
    
        18
  •  1
  •   smac89    7 年前

    这个也可以用来打印所有的答案

    public void recur(int[] a, int n, int sum, int[] ans, int ind) {
        if (n < 0 && sum != 0)
            return;
        if (n < 0 && sum == 0) {
            print(ans, ind);
            return;
        }
        if (sum >= a[n]) {
            ans[ind] = a[n];
            recur(a, n - 1, sum - a[n], ans, ind + 1);
        }
        recur(a, n - 1, sum, ans, ind);
    }
    
    public void print(int[] a, int n) {
        for (int i = 0; i < n; i++)
            System.out.print(a[i] + " ");
        System.out.println();
    }
    

    时间复杂度是指数的。2^n阶

        19
  •  1
  •   smac89    7 年前

    我在为scala任务做类似的事情。想在这里发布我的解决方案:

     def countChange(money: Int, coins: List[Int]): Int = {
          def getCount(money: Int, remainingCoins: List[Int]): Int = {
            if(money == 0 ) 1
            else if(money < 0 || remainingCoins.isEmpty) 0
            else
              getCount(money, remainingCoins.tail) +
                getCount(money - remainingCoins.head, remainingCoins)
          }
          if(money == 0 || coins.isEmpty) 0
          else getCount(money, coins)
        }
    
        20
  •  1
  •   smac89    7 年前

    Option Explicit
    
    Public Sub SumTarget()
        Dim numbers(0 To 6)  As Long
        Dim target As Long
    
        target = 15
        numbers(0) = 3: numbers(1) = 9: numbers(2) = 8: numbers(3) = 4: numbers(4) = 5
        numbers(5) = 7: numbers(6) = 10
    
        Call SumUpTarget(numbers, target)
    End Sub
    
    Public Sub SumUpTarget(numbers() As Long, target As Long)
        Dim part() As Long
        Call SumUpRecursive(numbers, target, part)
    End Sub
    
    Private Sub SumUpRecursive(numbers() As Long, target As Long, part() As Long)
    
        Dim s As Long, i As Long, j As Long, num As Long
        Dim remaining() As Long, partRec() As Long
        s = SumArray(part)
    
        If s = target Then Debug.Print "SUM ( " & ArrayToString(part) & " ) = " & target
        If s >= target Then Exit Sub
    
        If (Not Not numbers) <> 0 Then
            For i = 0 To UBound(numbers)
                Erase remaining()
                num = numbers(i)
                For j = i + 1 To UBound(numbers)
                    AddToArray remaining, numbers(j)
                Next j
                Erase partRec()
                CopyArray partRec, part
                AddToArray partRec, num
                SumUpRecursive remaining, target, partRec
            Next i
        End If
    
    End Sub
    
    Private Function ArrayToString(x() As Long) As String
        Dim n As Long, result As String
        result = "{" & x(n)
        For n = LBound(x) + 1 To UBound(x)
            result = result & "," & x(n)
        Next n
        result = result & "}"
        ArrayToString = result
    End Function
    
    Private Function SumArray(x() As Long) As Long
        Dim n As Long
        SumArray = 0
        If (Not Not x) <> 0 Then
            For n = LBound(x) To UBound(x)
                SumArray = SumArray + x(n)
            Next n
        End If
    End Function
    
    Private Sub AddToArray(arr() As Long, x As Long)
        If (Not Not arr) <> 0 Then
            ReDim Preserve arr(0 To UBound(arr) + 1)
        Else
            ReDim Preserve arr(0 To 0)
        End If
        arr(UBound(arr)) = x
    End Sub
    
    Private Sub CopyArray(destination() As Long, source() As Long)
        Dim n As Long
        If (Not Not source) <> 0 Then
            For n = 0 To UBound(source)
                    AddToArray destination, source(n)
            Next n
        End If
    End Sub
    

    输出(写入即时窗口)应为:

    SUM ( {3,8,4} ) = 15
    SUM ( {3,5,7} ) = 15
    SUM ( {8,7} ) = 15
    SUM ( {5,10} ) = 15 
    
        21
  •  1
  •   smac89    7 年前

    PHP版本 ,灵感来自Keith Beller的C#版本。

    /**
     * Calculates a subset sum: finds out which combinations of numbers
     * from the numbers array can be added together to come to the target
     * number.
     * 
     * Returns an indexed array with arrays of number combinations.
     * 
     * Example: 
     * 
     * <pre>
     * $matches = subset_sum(array(5,10,7,3,20), 25);
     * </pre>
     * 
     * Returns:
     * 
     * <pre>
     * Array
     * (
     *   [0] => Array
     *   (
     *       [0] => 3
     *       [1] => 5
     *       [2] => 7
     *       [3] => 10
     *   )
     *   [1] => Array
     *   (
     *       [0] => 5
     *       [1] => 20
     *   )
     * )
     * </pre>
     * 
     * @param number[] $numbers
     * @param number $target
     * @param array $part
     * @return array[number[]]
     */
    function subset_sum($numbers, $target, $part=null)
    {
        // we assume that an empty $part variable means this
        // is the top level call.
        $toplevel = false;
        if($part === null) {
            $toplevel = true;
            $part = array();
        }
    
        $s = 0;
        foreach($part as $x) 
        {
            $s = $s + $x;
        }
    
        // we have found a match!
        if($s == $target) 
        {
            sort($part); // ensure the numbers are always sorted
            return array(implode('|', $part));
        }
    
        // gone too far, break off
        if($s >= $target) 
        {
            return null;
        }
    
        $matches = array();
        $totalNumbers = count($numbers);
    
        for($i=0; $i < $totalNumbers; $i++) 
        {
            $remaining = array();
            $n = $numbers[$i];
    
            for($j = $i+1; $j < $totalNumbers; $j++) 
            {
                $remaining[] = $numbers[$j];
            }
    
            $part_rec = $part;
            $part_rec[] = $n;
    
            $result = subset_sum($remaining, $target, $part_rec);
            if($result) 
            {
                $matches = array_merge($matches, $result);
            }
        }
    
        if(!$toplevel) 
        {
            return $matches;
        }
    
        // this is the top level function call: we have to
        // prepare the final result value by stripping any
        // duplicate results.
        $matches = array_unique($matches);
        $result = array();
        foreach($matches as $entry) 
        {
            $result[] = explode('|', $entry);
        }
    
        return $result;
    }
    
        22
  •  1
  •   feihcsim    7 年前

    建议回答:

    generators :

    function* subsetSum(numbers, target, partial = [], partialSum = 0) {
    
      if(partialSum === target) yield partial
    
      if(partialSum >= target) return
    
      for(let i = 0; i < numbers.length; i++){
        const remaining = numbers.slice(i + 1)
            , n = numbers[i]
    
        yield* subsetSum(remaining, target, [...partial, n], partialSum + n)
      }
    
    }
    

    numbers

        23
  •  1
  •   Redu    5 年前

    首先推导出0。零是加法的恒等式,因此在这种特殊情况下,它不适用于幺半群定律。如果你想爬到正数,也可以推导出负数。否则你还需要做减法运算。

    所以。。。在这个特定的作业中,最快的算法如下JS所示。

    function items2T([n,...ns],t){
        var c = ~~(t/n);
        return ns.length ? Array(c+1).fill()
                                     .reduce((r,_,i) => r.concat(items2T(ns, t-n*i).map(s => Array(i).fill(n).concat(s))),[])
                         : t % n ? []
                                 : [Array(c).fill(n)];
    };
    
    var data = [3, 9, 8, 4, 5, 7, 10],
        result;
    
    console.time("combos");
    result = items2T(data, 15);
    console.timeEnd("combos");
    console.log(JSON.stringify(result));

    这是一个非常快速的算法,但是如果你对 data 阵列 会更快。使用 .sort() 许多的

        24
  •  0
  •   smac89    7 年前

    我把C样本移植到了客观C,但在回答中没有看到:

    //Usage
    NSMutableArray* numberList = [[NSMutableArray alloc] init];
    NSMutableArray* partial = [[NSMutableArray alloc] init];
    int target = 16;
    for( int i = 1; i<target; i++ )
    { [numberList addObject:@(i)]; }
    [self findSums:numberList target:target part:partial];
    
    
    //*******************************************************************
    // Finds combinations of numbers that add up to target recursively
    //*******************************************************************
    -(void)findSums:(NSMutableArray*)numbers target:(int)target part:(NSMutableArray*)partial
    {
        int s = 0;
        for (NSNumber* x in partial)
        { s += [x intValue]; }
    
        if (s == target)
        { NSLog(@"Sum[%@]", partial); }
    
        if (s >= target)
        { return; }
    
        for (int i = 0;i < [numbers count];i++ )
        {
            int n = [numbers[i] intValue];
            NSMutableArray* remaining = [[NSMutableArray alloc] init];
            for (int j = i + 1; j < [numbers count];j++)
            { [remaining addObject:@([numbers[j] intValue])]; }
    
            NSMutableArray* partRec = [[NSMutableArray alloc] initWithArray:partial];
            [partRec addObject:@(n)];
            [self findSums:remaining target:target part:partRec];
        }
    }
    
        25
  •  0
  •   smac89    7 年前

    @KeithBeller的回答稍微改变了变量名和一些注释。

        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);
            }
        }'
    
        26
  •  0
  •   niry    4 年前

    use strict;
    use List::Util qw/sum/;
    
    sub subset_sum {
      my ($numbers, $target, $partial) = @_;
    
      my $s = sum(@$partial);
      print 'sum('.join(',', @$partial).") = $target\n" if $s == $target;
      return if $s >= $target;
    
      subset_sum([@$numbers[$_ + 1 .. $#$numbers]], $target,
                 [@$partial, $numbers->[$_]]) for (0 .. $#$numbers);
    }
    
    subset_sum([3,9,8,4,5,7,10,6], 15);
    

    sum(3,8,4) = 15
    sum(3,5,7) = 15
    sum(9,6) = 15
    sum(8,7) = 15
    sum(4,5,6) = 15
    sum(5,10) = 15