查找向量中的第一个缺失元素

Posted

技术标签:

【中文标题】查找向量中的第一个缺失元素【英文标题】:Find First Missing Element in a vector 【发布时间】:2015-01-27 17:19:19 【问题描述】:

这个问题has been asked before 但我找不到 C++ 的问题。

如果我有一个向量并且我有一个起始数字,std::algorithm 是否为我提供了一种查找下一个最高缺失数字的方法?

我显然可以在嵌套循环中写这个,我只是无法摆脱我在重新发明***的感觉。

例如,给定:vector foo13,8,3,6,10,1,7,0;

起始编号0 应该找到2。 起始编号6 应该找到9。 起始编号-2 应该找到-1

编辑:

到目前为止,所有解决方案都需要排序。这实际上可能是必需的,但必须创建一个临时排序的 vector 来适应这种情况,因为 foo 必须保持不变。

【问题讨论】:

来到这里是希望有一个优雅的 STL 解决方案,它只使用一两行代码。但似乎这不存在 - 悲伤:-) @dhaumann 是的,我认为在这里挖掘最简单的东西,我仍然觉得它必须被整合到一个函数中。如果您的向量可以排序,您绝对可以简化。如果您询问排序向量的问题并将其链接到此处,我将提供 2 行 STL 解决方案。 @dhaumann 实际上取消它,您可以排序然后在此处查看接受的答案:***.com/q/27861373/2642059 【参考方案1】:

至少据我所知,没有标准算法可以直接实现您所要求的。

如果你想用 O(N log N) 复杂度来做,你可以从对输入进行排序开始。然后使用std::upper_bound 查找您要求的号码(如果存在)的(最后一个实例)。从那里,您会发现一个与前一个数字相差不止一个的数字。从那里您将扫描集合中连续数字之间大于 1 的差异。

在实际代码中执行此操作的一种方法是这样的:

#include <iostream>
#include <algorithm>
#include <vector>
#include <numeric>
#include <iterator>

int find_missing(std::vector<int> x, int number) 
    std::sort(x.begin(), x.end());
    auto pos = std::upper_bound(x.begin(), x.end(), number);

    if (*pos - number > 1)
        return number + 1;
    else 
        std::vector<int> diffs;
        std::adjacent_difference(pos, x.end(), std::back_inserter(diffs));
        auto pos2 = std::find_if(diffs.begin() + 1, diffs.end(), [](int x)  return x > 1; );
        return *(pos + (pos2 - diffs.begin() - 1)) + 1;
    


int main() 
    std::vector<int> x 13, 8, 3, 6, 10, 1,7, 0;

    std::cout << find_missing(x, 0) << "\n";
    std::cout << find_missing(x, 6) << "\n";

这比您通常认为的最佳方式要少一些,以提供可以/确实保持未排序(并且以任何方式未修改)的向量的外观。我通过创建向量的副本并在find_missing 函数中对副本进行排序来做到这一点。因此,原始向量保持不变。缺点很明显:如果向量很大,复制它可能/将会很昂贵。此外,这最终会为每个查询对向量进行排序,而不是排序一次,然后对其执行任意数量的查询。

【讨论】:

std::upper_bound 很有趣。但我不认为比较功能可以调整到足以使其在未排序的容器上工作。我真正想要的是无需排序就可以工作的东西,因为我无法对vector 进行排序。 我不知道adjacent_difference存在 @bolov:我一生中的(许多)任务之一是帮助使隐藏在&lt;numeric&gt; 中的算法更加可见。 :-) 我会在不久的将来尝试理解这一点:))。同时我编译并运行了一些测试用例:我发现它为不在容器中的数字提供了错误的输出:例如(elem:result) (-2:-1 , 5:9 , 20:21) @bolov:至少在我阅读这个问题时,这些看起来像是正确答案。【参考方案2】:

所以我想我会发布一个答案。我不知道 std::algorithm 中有什么可以直接完成此操作,但结合 vector&lt;bool&gt; 您可以在 O(2N) 中完成此操作。

template <typename T>
T find_missing(const vector<T>& v, T elem)
    vector<bool> range(v.size());
    elem++;

    for_each(v.begin(), v.end(), [&](const T& i)if((i >= elem && i - elem < range.size())range[i - elem] = true;);

    auto result = distance(range.begin(), find(range.begin(), range.end(), false));

    return result + elem;

【讨论】:

+1,但if(i - elem &lt; range.size()) 必须是if(i &gt;= elem &amp;&amp; i - elem &lt; range.size()),否则会发生可怕的事情。 @ruakh 谢谢我在测试中错过了。【参考方案3】:

首先您需要对向量进行排序。为此使用std::sort。

std::lower_bound 查找大于或等于给定元素的第一个元素。 (元素必须至少部分有序)

当你有连续的元素时,你从那里迭代。

处理重复:一种方法是我采用的方法:在迭代时考虑连续且相等的元素。另一种方法是添加向量/范围包含唯一元素的先决条件。我选择前者是因为它避免了擦除元素。

以下是从排序向量中消除重复项的方法:

v.erase(std::unique(v.begin(), v.end()), v.end());

我的实现:

// finds the first missing element in the vector v
// prerequisite: v must be sorted
auto firstMissing(std::vector<int> const &v, int elem) -> int 
  auto low = std::lower_bound(std::begin(v), std::end(v), elem);

  if (low == std::end(v) || *low != elem) 
    return elem;
  

  while (low + 1 != std::end(v) &&
         (*low == *(low + 1) || *low + 1 == *(low + 1))) 
    ++low;
  
  return *low + 1;

还有一个通用版本:

// finds the first missing element in the range [first, last)
// prerequisite: the range must be sorted
template <class It, class T = decltype(*std::declval<It>())>
auto firstMissing(It first, It last, T elem) -> T 
  auto low = std::lower_bound(first, last, elem);

  if (low == last || *low != elem) 
    return elem;
  

  while (std::next(low) != last &&
         (*low == *std::next(low) || *low + 1 == *std::next(low))) 
    std::advance(low, 1);
  
  return *low + 1;

测试用例:

int main() 
  auto v = std::vector<int>13, 8, 3, 6, 10, 1, 7, 7, 7, 0;    
  std::sort(v.begin(), v.end());

  for (auto n : -2, 0, 5, 6, 20) 
    cout << n << ": " << firstMissing(v, n) << endl;
  

  return 0;

结果:

-2: -2  
0: 2  
5: 5  
6: 9  
20: 20  

关于排序的说明:从 OP 的 cmets 中,他正在寻找不会修改向量的解决方案。

您必须对向量进行排序以获得有效的解决方案。如果修改矢量不是一个选项,您可以创建一个副本并对其进行处理。

如果你执意不排序,有一个蛮力解决方案(非常非常低效 - O(n^2)):

auto max = std::max_element(std::begin(v), std::end(v));
if (elem > *max) 
  return elem;

auto i = elem;
while (std::find(std::begin(v), std::end(v), i) != std::end(v)) 
  ++i;

return i;

【讨论】:

在我看来upper_bound 在这里比lower_bound 更有意义。如果请求的号码有多个实例,您需要最后一个,而不是第一个。 @JerryCoffin 我想过,但是使用 upper_bound 你无法判断你的元素是否在范围内。如果不是,则该元素是第一个缺失的。 似乎if (*result == input_number) 是一种非常简单的方法来确定返回的迭代器是否引用了更高数字的输入数字。另请注意,如果数字不存在,lower_boundupper_bound 都将返回完全相同的结果(迭代器指向比请求的更大的项)。 @JerryCoffin 是的。但是,如果容器中有重复项,那么无论 lower_bound 或 upper_bound,alg 都会惨遭失败。 (将发现第一个重复项作为第一个丢失)将编辑。 @bolov 感谢您的解决方案!从所有答案看来,处理此问题的唯一方法似乎是对vector 进行排序,这让我感到难过,但事实就是如此。【参考方案4】:

第一个解决方案:

对向量进行排序。找到起始号码,看看接下来是什么号码。 这将需要 O(NlogN),其中 N 是向量的大小。

第二种解决方案:

如果数字范围很小,例如(0,M) 您可以创建大小为 M 的布尔向量。对于每个初始向量的数量,使该索引的布尔值为真。稍后您可以通过检查布尔向量来查看下一个丢失的数字。这将花费 O(N) 时间和 O(M) 辅助内存。

【讨论】:

我认为 OP 正在寻找更具体的惯用 C++ 的东西,而不是对可以使用的基本算法的一般描述。 (但是,+1。并非所有东西都能从 STL 化中受益,即使在可能的情况下也是如此。) 顺便说一句,即使 M 不小,您的第二种方法也可以通过忽略 [x + 1、x + N]. 是的,辅助内存将是 O(N) 的 O(M) @Ashot 我的问题是我无法对向量进行排序,否则我只需要维护一个排序的向量并执行此操作:***.com/q/27861373/2642059 我目前的解决方案是维护一个vector&lt;bool&gt;。这可能是最好的:(

以上是关于查找向量中的第一个缺失元素的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中并行查找向量的第一个

C++:向量“损坏”的第一个元素

缺失的第一个正数

是否有用于查找向量中元素索引的 R 函数?

代码题(40)— 缺失的第一个正数缺失数字

查找第一个元素最近的行