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

标签 algorithm search language-agnostic combinations subset-sum

您将如何从给定的 N 数字集测试所有可能的加法组合,以便它们相加为给定的最终数字?

一个简单的例子:

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

最佳答案

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

此类算法在以下 Stanford's Abstract Programming lecture 中有很好的解释- 非常推荐观看此视频,以了解递归如何生成解决方案的排列。

编辑

以上作为生成器函数,使其更有用。由于 yield from,需要 Python 3.3+。

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 有点生疏,但我认为很容易理解。

C#转Java方案: (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 解决方案: (@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 过滤掉许多组合。

如果 NTarget 是大数字,则应转向解决方案的近似版本。

关于algorithm - 找到所有可能的数字组合以达到给定的总和,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4632322/

相关文章:

java - java中如何实现两级并行

python - 具有有限粒子分辨率的粒子群优化

language-agnostic - UML 场景示例

javascript - 正则表达式将短语与空格匹配

language-agnostic - 如何存储具有数十亿个节点和顶点的大型有向未加权图

language-agnostic - 单词结构的机器学习

java - 为什么这叫做回溯?

algorithm - 'n'数字的排列顺序

bash - 将 curl 输出管道化到 grep

php - sql复选框和多选过滤