我的问题是,我需要计算整数数组的总和与W
总和的数量。“
让我们说:
int array[] = {1,2,3,4,5};
我的算法只是找到从1
到W / minimum(array)
的所有长度组合,它等于W
,因为最小值为1。
如果每个组合的总和等于W,则检查每个组合然后增加一个计数器N
。
任何其他算法来解决这个问题?应该更快:))
更新 好吧,子集问题和背包问题都很好,但我的问题是数组的组合重复元素,如下所示:
1,1,1 -> the 1st combination
1,1,2
1,1,3
1,1,4
1,1,5
1,2,2 -> this combination is 1,2,2, not 1,2,1 because we already have 1,1,2.
1,2,3
1,2,4
1,2,5
1,3,3 -> this combination is 1,3,3, not 1,3,1 because we already have 1,1,3.
1,3,4
.
.
1,5,5
2,2,2 -> this combination is 2,2,2, not 2,1,1 because we already have 1,1,2.
2,2,3
2,2,4
2,2,5
2,3,3 -> this combination is 2,3,3, not 2,3,1 because we already have 1,2,3.
.
.
5,5,5 -> Last combination
这些都是长度为3的{1,2,3,4,5}
的组合。子集求和问题提供了另一种我不感兴趣的组合。
所以汇总到W
的组合,可以说是W = 7
,
2,5
1,1,5
1,3,3
2,2,3
1,1,2,3
1,2,2,2
1,1,1,1,3
1,1,1,2,2
1,1,1,1,1,2
1,1,1,1,1,1,1
更新
真实问题是重复的元素1,1,1
是必要的,并且生成的组合的顺序并不重要,因此1,2,1
与1,1,2
和2,1,1
相同。
答案 0 :(得分:9)
目前还没有有效的算法,也许永远不会(NP完全问题)。
这是({3}}的变体。
答案 1 :(得分:3)
这是coin change problem。它可以通过动态编程来解决,并且具有合理的W和设置大小
的限制答案 2 :(得分:0)
以下是Go中解决此问题的代码。我相信它在O(W / min(A))时间运行。评论应该足以看出它是如何工作的。重要的细节是它可以多次使用A中的元素,但是一旦它停止使用该元素,它就不会再使用它。这避免了重复计算[1,2,1]和[1,1,2]等事情。
package main
import (
"fmt"
"sort"
)
// This is just to keep track of how many times we hit ninjaHelper
var hits int = 0
// This is our way of indexing into our memo, so that we don't redo any
// calculations.
type memoPos struct {
pos, sum int
}
func ninjaHelper(a []int, pos, sum, w int, memo map[memoPos]int64) int64 {
// Count how many times we call this function.
hits++
// Check to see if we've already done this computation.
if r, ok := memo[memoPos{pos, sum}]; ok {
return r
}
// We got it, and we can't get more than one match this way, so return now.
if sum == w {
return 1
}
// Once we're over w we can't possibly succeed, so just bail out now.
if sum > w {
return 0
}
var ret int64 = 0
// By only checking values at this position or later in the array we make
// sure that we don't repeat ourselves.
for i := pos; i < len(a); i++ {
ret += ninjaHelper(a, i, sum+a[i], w, memo)
}
// Write down our answer in the memo so we don't have to do it later.
memo[memoPos{pos, sum}] = ret
return ret
}
func ninja(a []int, w int) int64 {
// We reverse sort the array. This doesn't change the complexity of
// the algorithm, but by counting the larger numbers first we can hit our
// target faster in a lot of cases, avoid a bit of work.
sort.Ints(a)
for i := 0; i < len(a)/2; i++ {
a[i], a[len(a)-i-1] = a[len(a)-i-1], a[i]
}
return ninjaHelper(a, 0, 0, w, make(map[memoPos]int64))
}
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
w := 1000
fmt.Printf("%v, w=%d: %d\n", a, w, ninja(a, w))
fmt.Printf("Hits: %v\n", hits)
}
答案 3 :(得分:0)
只是把它放到床上,这里是递归和(非常简单)动态编程解决这个问题。您可以通过使用更复杂的终止条件来减少递归解决方案的运行时间(但不是时间复杂度),但其主要目的是显示逻辑。
我见过的许多动态编程解决方案都保留了整个N x | c |结果数组,但这不是必需的,因为第i行可以从第i-1行生成,而且它可以按从左到右的顺序生成,因此不需要进行复制。
我希望这些评论有助于解释逻辑。 dp解决方案速度足够快,以至于我无法找到一个没有超长时间溢出的测试用例,这个时间超过了几毫秒;例如:
$ time ./coins dp 1000000 1 2 3 4 5 6 7
3563762607322787603
real 0m0.024s
user 0m0.012s
sys 0m0.012s
// Return the number of ways of generating the sum n from the
// elements of a container of positive integers.
// Note: This function will overflow the stack if an element
// of the container is <= 0.
template<typename ITER>
long long count(int n, ITER begin, ITER end) {
if (n == 0) return 1;
else if (begin == end || n < 0) return 0;
else return
// combinations which don't use *begin
count(n, begin + 1, end) +
// use one (more) *begin.
count(n - *begin, begin, end);
}
// Same thing, but uses O(n) storage and runs in O(n*|c|) time,
// where |c| is the length of the container. This implementation falls
// directly out of the recursive one above, but processes the items
// in the reverse order; each time through the outer loop computes
// the combinations (for all possible sums <= n) for sum prefix of
// the container.
template<typename ITER>
long long count1(int n, ITER begin, ITER end) {
std::vector<long long> v(n + 1, 0);
v[0] = 1;
// Initial state of v: v[0] is 1; v[i] is 0 for 1 <= i <= n.
// Corresponds to the termination condition of the recursion.
auto vbegin = v.begin();
auto vend = v.end();
for (auto here = begin; here != end; ++here) {
int a = *here;
if (a > 0 && a <= n) {
auto in = vbegin;
auto out = vbegin + a;
// *in is count(n - a, begin, here).
// *out is count(n, begin, here - 1).
do *out++ += *in++; while (out != vend);
}
}
return v[n];
}