您将如何测试给定 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中有很好的解释。 - 非常推荐此视频来了解递归如何生成解决方案的排列。

编辑

上面作为生成器函数,使其更有用一些。需要 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) 

这是相同算法的 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 有点生疏,但我认为很容易理解。

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

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 是大数,则应采用解决方案的近似版本。


评论关闭
IT源码网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!