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

Posted

技术标签:

【中文标题】计算具有给定总和的子集的有效方法【英文标题】:Efficient way to count subsets with given sum 【发布时间】:2015-03-01 19:28:51 【问题描述】:

给定 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 个测试用例。因此,如果他们存在比这个更好的解决方案,请提供帮助

【问题讨论】:

【参考方案1】:

N 很小(2^20 - 大约是 100 万 - 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;

【讨论】:

注意==的优先级高于&amp;,所以currentSet &amp; 1 == 1被解析为currentSet &amp; (1 == 1)【参考方案2】:

您可以利用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)

基于中间相遇技术的更快解决方案:

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

    现在让我们生成后半部分的所有子集,并为每个子集添加前半部分总和为 S - currentSum e 的子集数量(使用 1. 中构建的映射),其中 @ 987654330@ 是当前子集中所有元素的总和。同样,我们有O(2 ** (N / 2)) 子集,每个子​​集都在O(1) 中处理。

总时间复杂度为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 是奇数,则中间元素可以转到前半部分或后半部分。只要它恰好到达其中之一,它去哪里都没有关系。

【讨论】:

你能发布最有效率的帖子吗? 我们可以通过使用 memoisation 或 DP 使其更快吗? @user3840069 可以得到类似O(2 ** (N / 2)) 的东西,但有必要吗?您对此解决方案的性能有任何疑问吗?在我看来,它应该在给定的限制下快速工作。 不,我有问题。我想要它更快。你能添加那个代码吗? @user3840069 我已经添加了对该算法的描述。

以上是关于计算具有给定总和的子集的有效方法的主要内容,如果未能解决你的问题,请参考以下文章

改进子集解决方案

为什么我的子集总和方法不正确?

计算 Mat OpenCV 子集的总和

计算组合的有效算法,数组的重复加起来等于给定的总和

为啥我的子集总和方法不正确?

如何递归枚举添加到给定总和的所有子集?