计算具有给定总和的子集的有效方法
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;
【讨论】:
注意==
的优先级高于&
,所以currentSet & 1 == 1
被解析为currentSet & (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 我已经添加了对该算法的描述。以上是关于计算具有给定总和的子集的有效方法的主要内容,如果未能解决你的问题,请参考以下文章