【问题标题】:All possible numbers formed from addition of given digits由给定数字相加形成的所有可能数字
【发布时间】:2015-03-06 21:25:51
【问题描述】:

如果我有 n-r 数字,而 1 to n 之间缺少 r 数字,那么我如何计算可以通过添加这些数字形成的所有可能数字(以 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 中的第一个数字,我无法使用给定数字的组合形成。一个简单的逻辑就可以了。谢谢!

【问题讨论】:

  • 您只想要第一个无法形成的数字吗?还是所有你无法形成的数字?还是你能组成的所有数字?
  • 只有第一个我无法形成的数字是我感兴趣的。就是这样。
  • 我建议先用纸笔开发一个算法,然后再实现。
  • *8 - 不能形成,但可以通过添加组 {6,2} 形成 8?
  • 这就是我写的,我们只有从 1 到 n 的数字,其中一些数字丢失了。在示例中,我们有从 1 到 5 的数字,其中删除了 2 个数字,我们必须从重新计算的数字中找出结果。

标签: c++ algorithm


【解决方案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());
            }
        }
    }
}

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

【讨论】:

    【解决方案2】:

    S[i] 是可以从您的数字中的第一个i 组成的一组数字。然后给定S[i],很容易构造S[i+1]:以S[i]开头,然后将s+r形式的所有数字相加,其中sS[i]中,r就是@987654330 @-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}
    

    【讨论】:

      【解决方案3】:
      1. 以包含 0 的 set 开头
      2. 找出 set 和您的第一个数字的所有可能总和(因此,如果您的第一个数字是 1,那么您的 set 包含 1 和 0)
      3. 现在添加下一个数字(因此,如果您的下一个数字是 2,则您的 set 包含 0、1、2 和 3)
      4. 等等……

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

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

      set&lt;int&gt; foo{ 0 };
      vector&lt;int&gt; numbers{ 4, 1, 2 };

      for (auto&amp; i : numbers){
      set&lt;int&gt; temp;

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

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

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

      例如,这将不起作用:

      auto i = foo.begin();
      while (++i != prev(foo.end()) &amp;&amp; *prev(i) + 1 == *i);
      cout &lt;&lt; "First number that cannot be formed " &lt;&lt; *prev(i) + 1 &lt;&lt; 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。

      【讨论】:

        【解决方案4】:

        对于只包含正整数的输入sets 或multisets,Evgeny Kluev's SolutionO(2n),[我的其他解决方案] 是 O(nlogn)。

        对于给定的输入numbers,您可以找到如下不可组合的数字:

        1. number 的元素收集到int_log[i] 中,其中i = int(log2(element))
        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;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-03-27
          • 1970-01-01
          • 2013-03-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-11-23
          相关资源
          最近更新 更多