找到所有可能的数字组合以达到给定的总和

Jam*_* P. 207 language-agnostic algorithm search combinations subset-sum

您将如何测试一组给定数字的所有可能的添加组合,以便它们加起来给定的最终数字?

例:

  • 要添加的数字集:{1,5,22,15,0,...}
  • 期望的结果:12345

Man*_*res 221

这个问题可以通过过滤掉那些到达目标的所有可能总和的递归组合来解决.这是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
Run Code Online (Sandbox Code Playgroud)

这种类型的算法在以下Standford的抽象编程讲座中得到了很好的解释- 这个视频非常值得推荐,以了解递归如何产生解决方案的排列.

编辑

以上作为生成器函数,使其更有用.由于需要Python 3.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)
Run Code Online (Sandbox Code Playgroud)

这是相同算法的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Java解决方案的C#转换:( 来自@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Ruby解决方案:( 来自@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)
Run Code Online (Sandbox Code Playgroud)

编辑:复杂性讨论

正如其他人所说,这是一个NP难问题.它可以在指数时间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是大数字,则应该转变为解决方案的近似版本.

  • 它也不适用于每个组合,例如`[1,2,5],5`只输出`[5]`,当`[1,1,1,1,1]`,`[2, 2,1]`和`[2,1,1,1]`是解决方案. (10认同)
  • 此解决方案不适用于所有情况.考虑`[1,2,0,6,-3,3],3` - 它只输出`[1,2],[0,3],[3]`而丢失的情况如`[6, - 3,3]` (3认同)
  • @cbrad是因为`remaining = numbers [i + 1:]`中的`i + 1`.看起来该算法不允许重复. (3认同)

小智 32

这个问题的解决方案已在互联网上获得了一百万次.这个问题被称为硬币改变问题.您可以在http://rosettacode.org/wiki/Count_the_coins找到解决方案,并在http://jaqm.ro/issues/volume-5,issue-2/pdfs/patterson_harmel.pdf(或Google 硬币更改)找到它的数学模型问题).

顺便说一下,Tsagadai的Scala解决方案很有意思.此示例生成1或0.作为副作用,它在控制台上列出所有可能的解决方案.它显示解决方案,但无法以任何方式使其可用.

为了尽可能有用,代码应返回a 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")
}
Run Code Online (Sandbox Code Playgroud)

运行时,它显示:

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)
Run Code Online (Sandbox Code Playgroud)

sumCombinations()功能可以单独使用,并且可以进一步分析结果以显示"最佳"解决方案(最短列表)或解决方案的数量(列表的数量).

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

例如,我们可能会认为List(5, 10)应该给出两种组合:List(5, 10)List(10, 5).因为List(5, 5, 5)它可以提供三种组合或仅一种,具体取决于要求.对于整数,这三种排列是等价的,但如果我们处理硬币,就像在"硬币改变问题"中那样,它们就不是.

在要求中也没有说明每个数字(或硬币)是否只能使用一次或多次的问题.我们可以(并且我们应该!)将问题概括为每个数字的出现列表.这在现实生活中转化为"用一组硬币(而不是一组硬币值)赚取一定数量金钱的可能方式".最初的问题只是这个问题的一个特例,我们根据需要为每个硬币值制作总金额.

  • 这个问题与硬币变化问题并不完全相同.OP要求所有组合,而不仅仅是最小化.而且,据推测,集合中的整数可能是负数.因此,对于这个问题,硬币改变问题的某些优化是不可能的. (13认同)
  • 而且这个问题允许重复项目,我不确定OP想要这个,但更多的是背包问题 (5认同)

eph*_*ent 31

Haskell:

filter ((==) 12345 . sum) $ subsequences [1,5,22,15,0,..]
Run Code Online (Sandbox Code Playgroud)

J:

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

您可能会注意到,两者都采用相同的方法并将问题分为两部分:生成电源组的每个成员,并检查每个成员的总和与目标.

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

您是否需要任何一方的帮助,或找到不同的方法?

  • @HartCO派对有点晚了,但是`import Data.List` (4认同)
  • 哇,这是一些非常简洁的代码.我的答案很好.我想我只需要阅读一般的算法.当你激起我的好奇心时,我会看看这两种语言的语法. (3认同)

rba*_*ani 27

一个Javascript版本:

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
Run Code Online (Sandbox Code Playgroud)

  • @Redu ...例如一个简单的方法是,我们可以稍微修改算法并使用内部函数:https://jsbin.com/lecokaw/edit?js,console (2认同)
  • 给出的代码不一定能得到所有组合..例如,输入 [1,2],3 只会返回 1 + 2 = 3 而不是 1 + 1 + 1 或 2 + 1 (2认同)

bti*_*lly 17

到目前为止,解决方案有很多,但都是“生成然后过滤”的形式。这意味着他们可能会花费大量时间在无法得出解决方案的递归路径上。

这是一个解决方案O(size_of_array * (number_of_sums + number_of_solutions))。换句话说,它使用动态编程来避免枚举永远不会匹配的可能解决方案。

为了咯咯地笑,我用正数和负数进行了这项工作,并将其设为迭代器。它将适用于 Python 2.3+。

def subset_sum_iter(array, target):
    sign = 1
    array = sorted(array)
    if target < 0:
        array = reversed(array)
        sign = -1
    # Checkpoint A

    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]
    # Checkpoint B

    # 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
Run Code Online (Sandbox Code Playgroud)

这是一个与数组和目标一起使用的示例,其中其他解决方案中使用的过滤方法实际上永远不会完成。

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)
Run Code Online (Sandbox Code Playgroud)

这将在 2 秒内打印所有 522 个答案。以前的方法如果能在宇宙当前的生命周期中找到任何答案就很幸运了。(整个空间有2^168 = 3.74144419156711e+50可能的组合来运行。这......需要一段时间。)


解释 我被要求解释代码,但解释数据结构通常更能说明问题。所以我将解释数据结构。

让我们考虑一下subset_sum_iter([-2, 2, -3, 3, -5, 5, -7, 7, -11, 11], 10)

在检查点 A,我们意识到我们的目标是积极的,所以sign = 1。我们已经对输入进行了排序,以便array = [-11, -7, -5, -3, -2, 2, 3, 5, 7, 11]. 由于我们最终通过索引访问它很多,这里是从索引到值的映射:

0: -11
1:  -7
2:  -5
3:  -3
4:  -2
5:   2
6:   3
7:   5
8:   7
9:  11
Run Code Online (Sandbox Code Playgroud)

通过检查点 B,我们使用动态编程来生成last_index数据结构。它包含什么?

last_index = {    
    -28: [4],
    -26: [3, 5],
    -25: [4, 6],
    -24: [5],
    -23: [2, 4, 5, 6, 7],
    -22: [6],
    -21: [3, 4, 5, 6, 7, 8],
    -20: [4, 6, 7],
    -19: [3, 5, 7, 8],
    -18: [1, 4, 5, 6, 7, 8],
    -17: [4, 5, 6, 7, 8, 9],
    -16: [2, 4, 5, 6, 7, 8],
    -15: [3, 5, 6, 7, 8, 9],
    -14: [3, 4, 5, 6, 7, 8, 9],
    -13: [4, 5, 6, 7, 8, 9],
    -12: [2, 4, 5, 6, 7, 8, 9],
    -11: [0, 5, 6, 7, 8, 9],
    -10: [3, 4, 5, 6, 7, 8, 9],
    -9: [4, 5, 6, 7, 8, 9],
    -8: [3, 5, 6, 7, 8, 9],
    -7: [1, 4, 5, 6, 7, 8, 9],
    -6: [5, 6, 7, 8, 9],
    -5: [2, 4, 5, 6, 7, 8, 9],
    -4: [6, 7, 8, 9],
    -3: [3, 5, 6, 7, 8, 9],
    -2: [4, 6, 7, 8, 9],
    -1: [5, 7, 8, 9],
    0: [-1, 5, 6, 7, 8, 9],
    1: [6, 7, 8, 9],
    2: [5, 6, 7, 8, 9],
    3: [6, 7, 8, 9],
    4: [7, 8, 9],
    5: [6, 7, 8, 9],
    6: [7, 8, 9],
    7: [7, 8, 9],
    8: [7, 8, 9],
    9: [8, 9],
    10: [7, 8, 9]
}
Run Code Online (Sandbox Code Playgroud)

(旁注,它不是对称的,因为该条件if 0 < (new_s - target) * sign阻止我们记录过去的任何内容target,在我们的例子中是 10。)

这是什么意思?好吧,接受条目,10: [7, 8, 9]。这意味着我们最终可以得到 的最终总和,10最后选择的数字位于索引 7、8 或 9 处。即最后选择的数字可以是 5、7 或 11。

让我们仔细看看如果我们选择索引 7 会发生什么。这意味着我们以 5 结尾。因此,在我们到达索引 7 之前,我们必须得到 10-5 = 5。5 的条目读取为5: [6, 7, 8, 9]。所以我们可以选择索引 6,即 3。虽然我们在索引 7、8 和 9 处到达 5,但在索引 7 之前我们没有到达那里。所以我们倒数第二个选择必须是索引 6 处的 3 。

现在我们必须在索引 6 之前到达 5-3 = 2。条目 2 为:2: [5, 6, 7, 8, 9]。同样,我们只关心索引处的答案,5因为其他答案发生得太晚了。所以倒数第三个选择必须是索引 5 处的 2。

最后我们必须在索引 5 之前到达 2-2 = 0。条目 0 为:0: [-1, 5, 6, 7, 8, 9]。同样,我们只关心-1. 但它-1不是一个索引 - 事实上我用它来表示我们已经完成选择。

所以我们刚刚找到了解决方案2+3+5 = 10。这是我们打印出来的第一个解决方案。

现在我们进入子recur功能。因为它是在我们的 main 函数内部定义的,所以它可以看到last_index.

首先要注意的是,它调用yield,而不是return. 这使它成为一个发电机。当您调用它时,您将返回一种特殊类型的迭代器。当您循环该迭代器时,您将获得它可以产生的所有内容的列表。但是当它生成它们时你就会得到它们。如果列表很长,则不会将其放入内存中。(这很重要,因为我们可以得到一个很长的清单。)

将产生的结果recur(new_target, max_i)是您可以总结为new_target仅使用array具有最大索引的元素的所有方法max_inew_target这就是它的答案:“我们必须在 index 之前到达max_i+1。” 当然,它是递归的。

因此recur(target, len(array)),所有解决方案都可以使用任何索引来达到目标​​。这就是我们想要的。


小智 11

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>());
}
Run Code Online (Sandbox Code Playgroud)


小智 9

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;
}
Run Code Online (Sandbox Code Playgroud)


Ber*_*nat 9

Java非递归版本,只是不断添加元素并将它们重新分配给可能的值。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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输入示例:

numbers: 0, 1, 2, 2, 5, 10, 20
targets: 4, 10, 25
Run Code Online (Sandbox Code Playgroud)

示例输出:

## 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
Run Code Online (Sandbox Code Playgroud)


bal*_*ala 5

Thank you.. ephemient
Run Code Online (Sandbox Code Playgroud)

我已将上述逻辑从 python 转换为 php ..

Thank you.. ephemient
Run Code Online (Sandbox Code Playgroud)


小智 5

另一个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)
Run Code Online (Sandbox Code Playgroud)

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