C++程序,计算给定数字的总和的所有可能性(允许重复)

gdrx4gfi  于 2023-04-01  发布在  其他
关注(0)|答案(2)|浏览(172)

我需要用C++写一个程序,它需要一组数字(特别是1和2),然后计算这些数字可以有多少种组合。
例如,如果我们想要的总和是'3'。那么程序将返回有3种可能的组合(1+1+1,1+2,2+1)。我最困惑的是如何计算能够多次使用列表中的数字。
如果可能的话,我也想知道他们是否是解决这个问题的非递归方法。伪代码也可以。我只是想知道一个程序的结构,可以满足这一点。
我最初的计划如下:

int SumCombinations(int numberOfcombos)
{

    if (numberOfcombos == 0)
    {
        return 0;
    }
    int results = 1; //will always be at least one arrangement 

    if (numberOfcombos % 2 == 0)
    {
        results++; //can be made up of only '2' values
    }

    int temp = 0;
    while (temp < numberOfcombos)
    {
        temp += 1;
        temp += 2;
    }
    if (temp == numberOfcombos)
    {
        results++;
    }
    temp = 0;
    while (temp < numberOfcombos)
    {
        temp += 2;
        temp += 1;
    }
    if (temp == numberOfcombos)
    {
        results++;
    }

        return results;
}

因为我们可以使用重复的“1”值,它们总是至少有1个可能的结果,如果总和是偶数,那么我们知道所有的“2”值也是可行的。

nwlqm0z1

nwlqm0z11#

看起来像一些新的问题,但你遇到了非常著名的问题称为“硬币变化”。因为它是非常古老的,有吨的解决方案可用。
你需要做的给定问题的转换是,你的总和是“变化”,你的硬币是1和2。
你可以在here或者Wikipedia上找到算法的解释。所有语言的解决方案都可以在rosetta code上找到。
这个想法是,你把总和,然后减去各种现有的元素(硬币)从这个总和。如果你达到0,那么你找到了一个解决方案。如果它低于0,那么,显然,这不是一个解决方案。
这种方法可以非常简单地使用一个平凡的递归算法来实现。但是你会在youtub上找到许多显示非递归,基于DP(动态编程)的代码的解决方案。
由于它的简单性,我将在这里展示递归方法:

#include <iostream>
#include <vector>
#include <algorithm>

long countRrecursive(const int sum, const std::vector<long>& input, long count, std::vector<long>::const_iterator current) {
    if (sum == 0) return 1;     // Solution found. Add 1 to the count
    if (sum < 0)  return 0;     // No solution found. This path will not be counted

    for (auto iter = current; iter != input.end(); ++iter)  // Try all possible paths
        count += countRrecursive(sum - *iter, input, 0, iter);
    return count;
}
int main() {
    int sum =4;                                 // The sum that we are looking for
    std::vector<long> elements = { 1,2 };       // And the elements that should build the sum

    std::sort(elements.begin(), elements.end(), std::greater<long>());
    std::cout << "Count: " << countRrecursive(sum, elements, 0, elements.begin()) << '\n';
}
bnl4lu3b

bnl4lu3b2#

好吧,为了得到一个关于发生了什么的线索,我们可能会尝试实现一个简单的计数方法。
显然,为了从1和2创建数字,我们可以从1和2的一些初始序列开始,例如数字7:

1111111
11111 2
111  22
1   222

或者在一般情况下:

(sum - 0) x 1     0 x 2
(sum - 2)         1 x 2
...
(sum - n) x 1     n x 2  with n == sum/2 (integer division)

对于上面的每个序列,我们需要计算的所有排列(我们可能确实创建了这些序列,并使用std::next_permutation作为原始算法),以获得所有可能的组合。
现在让我们更仔细地检查一下这些组合,特别是如果有一种方法可以从它们的前一个数字推导出新的数字的组合。显然,一个数字n可以计算为1 + (n - 1)2 + (n - 2)。因此,如果我们有n - 1n - 2的组合,我们可以简单地预先(或者附加)一个1n - 1的所有组合,一个2n - 2的所有组合。我们不能通过这种方式得到任何重复(假设在前代中没有重复):来自不同前辈的组合计算出不同的数字,因此这些组合中自然不可能有重复;我们产生两组新的组合,其中一组的所有成员具有初始1,另一组的所有成员具有初始2,因此再次没有可能的重复。
实际上缺乏的是证明我们不会错过任何可能的组合这种方式(我可能会补充后;你似乎不依赖于无论如何)。
无论如何,我们可以得出结论:
给定sum的新组合的数量是其两个前代组合的数量之和,即combinations(sum) = combinations(sum - 1) + combinations(sum - 2),并且起始值为0(我们可以把它包括在内只有一个组合可能根本不包含任何被加数...)和1(同样只有一个可能的组合)我们得到著名的Fibonacci sequence。因此我们可以应用众所周知的封闭公式来计算序列(如果使用浮点计算,即double-您需要考虑舍入误差!)或任何现有的积分算法(不要天真地用递归实现,你会多次计算相同的总和!),例如:

size_t fibonacci(size_t n)
{
    if(n <= 1)
    {
        return 1;
    }
    size_t f0 = 1;
    size_t f1 = 1;

    while(--n)
    {
        std::swap(f0, f1);
        f1 += f0;
    }
    return f1;
}

或者使用查找表(因为不是线程安全的):

size_t fibonacci(size_t n)
{
    static std::vector<size_t> values({1, 1});
    if(n >= values.size())
    {
        values.reserve(n + 1);
        while(values.size() <= n)
        {
            values.push_back(values.back() + *(values.end() - 2));
        }
    }
    return values[n];
}

相关问题