如何(从固定列表中)选择一个数字序列,该序列将与目标数字相加
How to pick a sequence of numbers (from a fixed list) that will sum to a target number?
假设我有一个目标数字和一个可能的值列表,我可以选择这些值来创建一个序列,一旦对每个选择的数字求和,就会对目标进行求和:
target = 31
list = 2, 3, 4
possible sequence: 3 2 4 2 2 2 4 2 3 2 3 2
我想:
- 首先决定是否有任何序列可以到达目标
- 返回多个(可能的(序列中的一个
这是我的尝试:
#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。此外,如果列表很大,而目标数量很高,则可能会导致大量的处理(导致大量检查失败(。
你将如何实现这种算法?
您建议的代码可能非常快,因为它是启发式的。但正如你所说,它可能会陷入一个几乎无穷无尽的循环中。
如果你想避免这种情况,你必须搜索一整套可能的组合。
抽象
让我们将我们的算法定义为函数f
,标量目标t
和向量<b>
作为返回系数向量<c>
的参数,其中<b>
和<c>
具有相同的维度:<c> = f(t, <b>)
首先,给定的数集Sg
应该被约简为它们的约简集Sr
,因此我们降低了解向量<c>
的维数。例如,{2,3,4,11}
可以被缩减为{2,3}
。我们通过递归调用我们的算法来获得这一点,方法是将Sg
拆分为一个新的目标ti
,剩余的数字作为新的给定集合Sgi
,并询问算法是否找到任何解(非零向量(。如果是,则从原始给定集合Sg
中移除该目标ti
。递归地重复此操作,直到找不到任何解决方案为止。
现在我们可以将这组数字理解为多项式,其中我们正在寻找可能的系数ci
,以获得我们的目标t
。让我们用i={1..n}
调用Sb
中的每个元素bi
。
我们的测试和ts
是ci * bi
的所有i
的和,其中每个ci
可以从0
运行到ni = floor(t/bi)
。
可能的测试次数N
现在是所有ni+1
:N = (n1+1) * (n2+1) * ... * (ni+1)
的乘积。
现在,通过将系数向量<c>
表示为整数向量并递增c1
,并将超限转移到向量中的下一个元素,重置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);
}
该代码打印
4 4 4 4 4 4 4 3
31 = +4*7 +3*1 +2*0
进一步思考
- 生产代码应该测试任何给定值中的零
- 如果评估的多项式已经超过目标值,则可以通过增加下一个系数来改进搜索
- 当c1被设置为零时,当计算目标值和评估多项式的差并且检查该差是否是b1的倍数时,可以进一步加速。如果不是,c2可以直接递增
- 也许存在一些利用最小公倍数的捷径
正如ihavenovidea所建议的,我也会尝试回溯。此外,我将按递减顺序对数字进行排序,以加快处理速度。
注意:评论比回答更合适,但我不允许这样做。希望它能有所帮助。如果有人要求,我将不回答这个问题。
- 比较并显示使用最小值(a,b)和最大值(a、b)升序排列的4个数字
- 为什么随机数生成器不在void函数中随机化数字,而在main函数中随机化
- 检查输入是否不是整数或数字
- 如何(从固定列表中)选择一个数字序列,该序列将与目标数字相加
- 如何用数字处理log(0)
- 最高有效数字侧的第N位
- 如何获取一个数字的前3位
- 查找最接近的大于当前数字的数字的索引
- C++A*算法并不总是在路径中具有目标节点
- 找到两对数字,使它们的乘积的绝对差最小化
- 我想做一个彼此不同但重复出现的数字
- 将数字转换为字母(例如:123 转换为一二三)
- C++如何计算用户输入的数字中的偶数位数
- 如何在C++中确定文本文件中的元素是字符还是数字
- 基于树莓pi的tensorflow lite量化ssd目标检测
- 使用数字列表和算术运算获取目标数字
- 我如何以多位数数字为目标
- 将目标数字拆分为一系列数字(递归任务)
- 在小于或等于目标的数组中查找最大的数字
- 给定一个整数数组,找出两个数字,使它们加起来等于一个特定的目标数