如何从固定列表中选择一系列数字,这些数字将加起来成为目标数字?

时间:2018-11-04 18:50:01

标签: c++ algorithm

假设我有一个目标编号和一个可能值的列表,我可以选择这些值来创建一个序列,该序列一旦与每个选取的数字相加,便会求和到目标:

target = 31
list = 2, 3, 4
possible sequence: 3 2 4 2 2 2 4 2 3 2 3 2 

我想:

  1. 首先确定是否有任何序列可以到达目标
  2. 返回许多(可能)序列之一

这是我的attempt

#include <iostream>
#include <random>
#include <chrono>
#include <vector>

inline int GetRandomInt(int min = 0, int max = 1) {
    uint64_t timeSeed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
    std::seed_seq ss{ uint32_t(timeSeed & 0xffffffff), uint32_t(timeSeed >> 32) };
    std::mt19937_64 rng;
    rng.seed(ss);
    std::uniform_int_distribution<int> unif(min, max);

    return unif(rng);
}

void CreateSequence(int target, std::vector<int> &availableNumbers) {
    int numAttempts = 1;
    int count = 0;
    std::vector<int> elements;

    while (count != target) {
        while (count < target) {
            int elem = availableNumbers[GetRandomInt(0, availableNumbers.size() - 1)];

            count += elem;
            elements.push_back(elem);
        }

        if (count != target) {
            numAttempts++;
            count = 0;
            elements.clear();
        }
    }

    int size = elements.size();
    std::cout << "count: " << count << " | " << "num elements: " << size << " | " << "num attempts: " << numAttempts << std::endl;
    for (auto it = elements.begin(); it != elements.end(); it++) {
        std::cout << *it  << " ";
    }   
}

int main() {
    std::vector<int> availableNumbers = { 2, 3, 4 };

    CreateSequence(31, availableNumbers);
}

但是如果数字列表不适合达到这样的总和,它可以无限循环。例如:

std::vector<int> availableNumbers = { 3 };

CreateSequence(8, availableNumbers);

3的序列的总和不等于8。此外,如果列表很大且目标数很高,则可能导致大量处理(导致很多检查失败)。

您将如何实现这种算法?

2 个答案:

答案 0 :(得分:2)

您的建议代码可能很快速,因为它是启发式的。但是正如您所说,它可能陷入几乎无限循环中。

如果要避免这种情况,则必须搜索整套可能的组合。

抽象

让我们将算法定义为函数f,并以标量目标t和向量<b>作为返回系数向量<c>的参数,其中{{1} }和<b>具有相同的尺寸:
<c>

首先,应将给定的数字集<c> = f(t, <b>)简化为缩减集Sg,以便我们减小解矢量Sr的维数。例如。 <c>可以减少为{2,3,4,11}。我们通过以下方式来递归调用算法:将{2,3}拆分为新的目标Sg,其余数字作为新的给定集合ti,并询问算法是否找到任何解决方案(非零向量)。如果是这样,请从原始给定集合Sgi中删除该目标ti。递归地重复此操作,直到找不到解决方案为止。

现在,我们可以将这组数字理解为多项式,在这里我们正在寻找可能的系数Sg以获取目标ci。让我们用t来调用Sb bi中的每个元素。

我们的测试总和i={1..n}ts的所有i的总和,其中每个ci * bi的范围可以从ci0

可能的测试数量ni = floor(t/bi)是所有Nni+1上的乘积。

现在通过将系数向量N = (n1+1) * (n2+1) * ... * (ni+1)表示为整数向量并递增<c>并遍历向量中的下一个元素,重置c1等等,对所有可能性进行迭代。 / p>

示例

c1

该代码打印

#include <random>
#include <chrono>
#include <vector>
#include <iostream>

using namespace std;

static int evaluatePolynomial(const vector<int> &base, const vector<int> &coefficients)
{
    int v=0;
    for(unsigned long i=0; i<base.size(); i++){
        v += base[i]*coefficients[i];
    }
    return v;
}

static bool isZeroVector(vector<int> &v)
{
    for (auto it = v.begin(); it != v.end(); it++) {
        if(*it != 0){
            return false;
        }
    } 
    return true;
}

static vector<int> searchCoeffs(int target, vector<int> &set) {
    // TODO: reduce given set

    vector<int> n = set;
    vector<int> c = vector<int>(set.size(), 0);

    for(unsigned long int i=0; i<set.size(); i++){
        n[i] = target/set[i];
    }

    c[0] = 1;

    bool overflow = false;
    while(!overflow){
        if(evaluatePolynomial(set, c) == target){
            return c;
        }

        // increment coefficient vector
        overflow = true;
        for(unsigned long int i=0; i<c.size(); i++){
            c[i]++;
            if(c[i] > n[i]){
                c[i] = 0;
            }else{
                overflow = false;
                break;
            }
        }
    }
    return vector<int>(set.size(), 0);
}

static void print(int target, vector<int> &set, vector<int> &c)
{
    for(unsigned long i=0; i<set.size(); i++){
        for(int j=0; j<c[i]; j++){
            cout << set[i] << " ";
        }
    }
    cout << endl;

    cout << target << " = ";
    for(unsigned long i=0; i<set.size(); i++){
        cout << " +" << set[i] << "*" << c[i];
    }
    cout << endl;
}

int main() {
    vector<int> set = {4,3,2};
    int target = 31;

    auto c = searchCoeffs(target, set);
    print(target, set,c);
}

其他想法

  • 生产代码应在任何给定值中测试零
  • 如果评估的多项式已经超过目标值,可以通过增加下一个系数来改善搜索。 如果在将c1设置为零时计算目标值和评估多项式的​​差,并检查该差是否为b1的倍数,则可以进一步提高速度。如果不是,则c2可以直接增加。
  • 也许存在一些利用最小公倍数的捷径

答案 1 :(得分:0)

按照ihavenoidea的建议,我还将尝试回溯。此外,我将按降序对数字进行排序,以加快处理速度。

注意:评论比答案更合适,但我不允许这样做。希望能帮助到你。如果需要,我将禁止回答。