由给定数字相加形成的所有可能的数字

All possible numbers formed from addition of given digits

本文关键字:数字 有可能      更新时间:2023-10-16

如果我有n-r个数字,从中间缺少r数字的1 to n,那么我如何计算这些数字相加可以形成的所有可能的数字(以 2/3/4/5/6 为一组......

例如,假设我有5-2数字,也就是说,1 2 43 5丢失了。现在,我可以形成

1 - {1}
2 - {2}
3 - {1,2}
4 - {4}
5 - {1,4}
6 - {4,2}
7 - {1,2,4}
8 - Cannot be formed

这是我需要找出的,这是我无法使用给定数字的组合形成 1 中的第一个数字。一个简单的逻辑就可以了。谢谢!

逻辑是为每个连续整数构建所有可能的总和,从 1 开始。通过跟踪所有可能的总和并仅检查整数对的总和,可以简化问题。伪代码(未经测试和有缺陷)如下所示:

const std::vector<unsigned> l = {1,2,4};
const unsigned sum = std::accumulate(l.begin(), l.end());
typedef std::vector<unsigned> Sum; // one possibility for a single value
typedef std::vector<Sum> Sums; // all possibilities for a single value
// the element all[i] will provide all possible subsets where the sum is equal to 'i'
std::vector<Sums> all(sum + 2); // we know that sum + 1 is impossible
// initialize with single values
for (auto i: l)
{
    all[i].push_back(Vector(1, i));
}
unsigned i = 1; // ignore 0
// stop as soon as a value doesn't have any subset
while (!all[i].empty())
{
    ++i;
    for (unsigned j = 1; i/2 > j; ++j)
    {
        const unsigned k = i - j;
        // as i == j+k, create all the relevant combinations
        for (const auto &sj: all[j])
        {
            for (const auto &sk: all[k])
            {
                all[i].push_back(sj);
                all[i].back.insert(all[i].end(), sk.begin(), sk.end());
            }
        }
    }
    if (0 == (i % 2))
    {
        // create all the possible decompositions out of i/2
        for (auto left = all[i/2].begin(); all[i/2].end() != left; ++left)
        {
            for (auto right = left + 1; all[i/2].end() != right; ++ right)
            {
                all[i].push_back(*left);
                all[i].back.insert(all[i].end(), right->begin(), right->end());
            }
        }
    }
}

需要解决的错误之一:拒绝相同数字多次出现的总和。

S[i]是一组数字,可以从数字的前i形成。然后给定S[i],很容易构造S[i+1]:从S[i]开始,然后将形式s+r的所有数字相加,其中sS[i]中,r是列表中(i+1)-th数字。

因此,你可以迭代地构建集合s[0] = {0}, S[1],...,S[n-r]S[n-r]包含所有可能的和。

以下是您的示例:

S[0] = {0}
r = 1: S[1] = {0} union {0+1} = {0,1}
r = 2: S[2] = {0,1} union {0+2,1+2} = {0,1,2,3}
r = 4: S[3] = {0,1,2,3} union {0+4,1+4,2+4,3+4} = {0,1,2,3,4,5,6,7}
  1. 从包含 0 的set开始
  2. 找到set和第一个数字的所有可能总和(因此,如果您的第一个数字是 1,那么您set包含 1 和 0)
  3. 现在添加下一个数字(所以如果你的下一个数字是 2,你的set包含 0、1、2 和 3)
  4. 等等...

set包含所有可能的数字。您需要遍历set并找到第一个间隙。即使您的集合中有负数,这也将起作用。

我知道你说你只是想要逻辑,但如果你想看到一个解决方案,鼠标悬停在下面:

<块引用类>


set<int> foo{ 0 };
vector<int> numbers{ 4, 1, 2 };

for (auto& i : numbers){
set<int> temp;

for_each(foo.begin(), foo.end(),[&](int j){temp.insert(i + j);});
foo.insert(temp.begin(), temp.end());
}

for (auto& i : foo){
cout << i << endl;
} cout << "First number that cannot be formed " << *mismatch(foo.begin(), prev(foo.end()), next(foo.begin()), [](int first, int second){return first + 1 == second;}).first + 1 << endl;

如果您尝试使用 STL 算法实现对set差距的搜索,我发现这有点困难。问题是您需要将迭代器的增量与其比较分开。

因此,例如,这是行不通的:


auto i = foo.begin();
while (++i != prev(foo.end()) && *prev(i) + 1 == *i); cout << "First number that cannot be formed " << *prev(i) + 1 << endl;

因为如果所有数字都是连续的*prev(i) + 1 是集合中的最后一个值。

此选项将起作用:

i = foo.begin();
while (i != prev(foo.end()) && *i + 1 == *next(i)){
    ++i;
}
cout << "First number that cannot be formed " << *i + 1 << endl;

但这假设您的set始终包含至少 0。

对于只包含正整数的输入set s 或 multiset s,Evgeny Kluev 的解是 O(2n),[我的另一个解] 是 O(nlogn)。

对于给定的输入number,您可以找到不可成形的数字,如下所示:

  1. number元素收集到int_log[i] i = int(log2(元素))
  2. 查找S[],这是前缀int_log[]的总和
  3. 第一个不可成形的数字是S[x] + 1其中int_log[x + 1]不包含小于或等于S[x] + 1的元素

有关范围 [0, S[i]] 中的所有数字都可以由小于 2i-1int_log子集形成的证明,请参阅:https://math.stackexchange.com/questions/1099359/0-sum-representable-by-numbers-in-a-set

set<int> numbers{ 1, 2, 5, 6, 7 };
unordered_multimap<unsigned long, decltype(numbers)::key_type> int_log;
for (auto& value : numbers){
    auto key = decltype(int_log)::key_type{};
    _BitScanReverse(&key, value); //If using gcc you'll need to use __builtin_clz here
    int_log.insert(make_pair(key, value));
}
auto result = decltype(numbers)::key_type{}; // Because we only ever need to look at the previous prefix sum value we'll use previousSum instead of a S[] here
auto sum = decltype(numbers)::key_type{};
auto i = decltype(int_log)::key_type{};
auto smallest = decltype(numbers)::key_type{};
do{
    result = sum;
    smallest = 2 << i;
    for (auto range = int_log.equal_range(i); range.first != range.second; ++range.first){
        const auto current = range.first->second;
        sum += current;
        smallest = min(smallest, current);
    }
    ++i;
} while (result >= smallest - 1);
cout << "First number that cannot be formed: " << result + 1 << endl;