使用给定总和计算子集的有效方法

时间:2015-03-01 19:28:52

标签: algorithm

给定N个数字,我需要计算其总和为S的子集。

注意:数组中的数字不必相同。

我目前的代码是:

int countSubsets(vector<int> numbers,int sum)
{
    vector<int> DP(sum+1);
    DP[0]=1;
    int currentSum=0;
    for(int i=0;i<numbers.size();i++)
    {
        currentSum+=numbers[i];
        for (int j=min(sum,currentSum);j>=numbers[i];j--)
            DP[j]+=DP[j - numbers[i]];
    }
    return DP[sum];
}

他们可以比这更有效吗?

约束是:

1 ≤ N ≤ 14
1 ≤ S ≤ 100000
1 ≤ A[i] ≤ 10000

他们在一个文件中也有100个测试用例。如果他们存在比这个更好的解决方案,请帮助

2 个答案:

答案 0 :(得分:1)

N很小(2 ^ 20 - 约为1 milion - 2 ^ 14是非常小的值) - 只是迭代所有子集,下面我写了很快的方法(bithacking)。将整数视为集合(按字典顺序列举子集)

int length = array.Length;
int subsetCount = 0;
for (int i=0; i<(1<<length); ++i)
{
    int currentSet = i;
    int tempIndex = length-1;
    int currentSum = 0;

    while (currentSet > 0) // iterate over bits "from the right side"
    {
       if (currentSet & 1 == 1) // if current bit is "1"
          currentSum += array[tempIndex];

       currentSet >>= 1;
       tempIndex--;        
    }
    subsetCount += (currentSum == targetSum) ? 1 : 0;
}

答案 1 :(得分:0)

您可以使用N很小的事实:可以生成给定数组的所有可能子集,并检查每个子集的总和是否为S。时间复杂度为O(N * 2 ** N)O(2 ** N)(取决于生成方式)。对于给定的约束,此解决方案应该足够快。

以下是O(2 ** N)解决方案的伪代码:

result = 0

void generate(int curPos, int curSum):
     if curPos == N:
         if curSum == S:
             result++
         return
     // Do not take the current element.
     generate(curPos + 1, curSum)
     // Take it.
     generate(curPos + 1, curSum + numbers[curPos])

generate(0, 0)

基于中间技术满足的更快解决方案:

  1. 让我们使用上面描述的算法生成数组前半部分的所有子集,并将它们的总和放入一个映射中(将总和映射到具有它的子集的数量。它可以是一个哈希表或者只是一个数组,因为S相对较小)。此步骤需要O(2 ** (N / 2))时间。

  2. 现在让我们为后半部分生成所有子集,并为每个子集添加上半部分总和为S - currentSum e的子集数(使用1中构造的映射),其中currentSum是当前子索引中所有元素的总和。我们再次提供O(2 ** (N / 2))个子集,并在O(1)中处理每个子集。

  3. 总时间复杂度为O(2 ** (N / 2))

    此解决方案的伪代码:

    Map<int, int> count = new HashMap<int, int>() // or an array of size S + 1.
    result = 0
    
    void generate1(int[] numbers, int pos, int currentSum):
        if pos == numbers.length:
            count[currentSum]++
            return
        generate1(numbers, pos + 1, currentSum)
        generate1(numbers, pos + 1, currentSum + numbers[pos])
    
    void generate2(int[] numbers, int pos, int currentSum):
        if pos == numbers.length:
            result += count[S - currentSum]
            return
        generate2(numbers, pos + 1, currentSum)
        generate2(numbers, pos + 1, currentSum + numbers[pos])
    
    generate1(the first half of numbers, 0, 0)
    generate2(the second half of numbers, 0, 0)
    

    如果N为奇数,则中间元素可以转到前半部分或第二部分。只要它恰好属于其中一个,它在哪里都没关系。