查找数字序列中的空白

Posted

技术标签:

【中文标题】查找数字序列中的空白【英文标题】:Finding gaps in sequence of numbers 【发布时间】:2008-12-18 21:38:22 【问题描述】:

我有一个 std::vector 包含一些数字,这些数字没有任何特定的顺序,并且数字之间可能有也可能没有间隔 - 例如,我可能有 1,2,3, 6 或 2,8,4,6 或 1, 9, 5, 2 等

我想要一个简单的方法来查看这个向量并说'给我最小的数字> = 1 没有出现在向量中'。所以,

对于上面的三个例子,答案分别是 4、1 和 3。

这不是性能关键,而且列表很短,因此例如复制和排序列表没有任何问题。

我并没有真正陷入困境,但我的 STL 技能严重萎缩,我觉得我要做一些不雅的事情 - 我很想看看其他人想出了什么。

【问题讨论】:

【参考方案1】:

您正在寻找的标准算法是std::adjacent_find

这是一个也使用 lambda 使谓词干净的解决方案:

int first_gap( std::vector<int> vec )

  // Handle the special case of an empty vector.  Return 1.
  if( vec.empty() )
    return 1;

  // Sort the vector
  std::sort( vec.begin(), vec.end() );

  // Find the first adjacent pair that differ by more than 1.
  auto i = std::adjacent_find( vec.begin(), vec.end(), [](int l, int r)return l+1<r; );

  // Handle the special case of no gaps.  Return the last value + 1.
  if ( i == vec.end() )
    --i;

  return 1 + *i;

【讨论】:

在很多方面这正是我所要求的。我将选择一个稍微不那么像 STL 的版本,因为我现在对无穷无尽的谓词函数有点失望。滚动 C++ lambdas。 这个问题对空向量有一个明确的答案。在一个空向量中,没有出现在向量中的最小整数 >= 1 是 1。在 STL 术语中:我们正在寻找最小的 N>=1 使得 find(begin,end,N)==end。 【参考方案2】:

选中的答案使用

int find_gap(std::vector<int> vec) 
    std::sort(vec.begin(), vec.end());
    int next = 1;
    for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) 
        if (*it != next) return next;
       ++next;
    
    return next;


find_gap(1,2,4,5) = 3
find_gap(2) = 1
find_gap(1,2,3) = 4

我没有传递对向量的引用,因为 a) 他说时间无关紧要 b) 所以我不会更改原始向量的顺序。

【讨论】:

【参考方案3】:

对列表进行排序然后进行线性搜索似乎是最简单的解决方案。根据列表的预期组成,您可以使用不太通用的排序算法,如果您自己实现排序,则可以在排序期间跟踪数据,以加快(或完全消除)搜索步骤。我认为这个问题没有什么特别优雅的解决方案

【讨论】:

【参考方案4】:

您可以分配一个位向量(与输入向量的长度相同),将其初始化为零,然后标记所有出现的索引(请注意,可以忽略大于长度的数字)。然后,返回第一个未标记的索引(如果所有索引都被标记,则返回长度,仅当所有索引在输入向量中恰好出现一次时才会发生)。

这应该比排序和搜索快。如果允许销毁原始文件,它将比排序使用更多的内存,但如果必须保留原始文件,它将比排序使用更少的内存。

【讨论】:

聪明的解决方案 粗略实现:ideone.com/5C1iQ3,稍作修改,始终在位向量中包含 0【参考方案5】:

实际上,如果您进行冒泡排序(您知道...他们先教您然后告诉您永远不要再使用的那个...),您将能够在排序的早期发现第一个空白过程,所以你可以停在那里。这应该会给你最快的整体时间。

【讨论】:

观察力不错。我想知道是否也可以调整第 k 个最小的算法,或者 nlogn 排序? 不错!它实际上是有用的。 仅当第一个间隙是一个相当小的数字时。在最坏的情况下,[1..size] 中的所有数字都会出现,您的运行时间将是 O(size^2) O() 表示法根据定义是最坏的情况。【参考方案6】:

排序-n-搜索:

std::sort(vec.begin(), vec.end());
int lowest = 1;
for(size_t ii = 1; ii < vec.size(); ++ii)

    if (vec[ii - 1] + 1 < vec[ii])
    
        lowest = (vec[ii - 1] + 1);
        break;
    


/* 1, 2, ..., N case */
if (lowest == vec[0]) lowest = (*vec.back()) + 1;

迭代器的使用意图与showcased in @joe_mucchiello's (ed: better) answer 一样清晰。

【讨论】:

好的,非常感谢。我刚刚做了类似的事情,但使用迭代器并处理最终情况。 1,2,3,4 应该返回 5 而不是 1。此代码在开始时考虑边缘条件,但在结束时不考虑。 @Will 和@Greg,已修复。迭代器留给读者作为练习;) 你应该只与最低的比较。你总是知道下一个值应该是什么。请参阅下面的答案。【参考方案7】:

好的,这是我的 2 美分。假设你有一个长度为 N 的向量。

    如果N 首先用min_element得到最小的元素,记为emin 调用 nth_element 获取 N/2 处的元素,称其为 ehalf 如果 ehalf != emin+N/2 左边有一个间隙,则通过在整个数组上调用 nth_element 递归地应用此方法,但要求元素 N/4。否则,在右边递归请求元素 3*N/4。

这应该比完全预先排序要好一些。

【讨论】:

您认为 emin+N/2 将位于索引 N/2 处的假设假定数组已经排序。 嗯,没有。我没想到。我调用了 nth_element 来查找要按排序顺序排列的元素。 nth_element 使用中值查找算法。有 和优化可能:您可以递归一半数组(第 n 个元素分区)。 顺便说一句:如果您只通过递归处理数组的必要一半进行优化,这比 sort-n-search 平均 查看我的答案以获得可能的实现。【参考方案8】:

你可以选择类似......

struct InSequence

    int _current;   bool insequence;
    InSequence() : _current(1), insequence(true)
    bool operator()(int x)          
        insequence = insequence ? (x == _current) : false;  
        _current++; 
        return insequence;
    
;

int first_not_in_sequence(std::vector<int>& v)

    std::sort(v.begin(), v.end());
    return 1+std::count_if(v.begin(), v.end(),InSequence());

【讨论】:

You insequence op() 中的测试过于复杂。你可以写得更简单更清楚:insequence = insequence &amp;&amp; x == _current;。一般来说,bool1 ? bool2 : bool3 总是可以写成bool1 &amp;&amp; bool2 || bool3(但它并不总是更易读)。 +1 用于向 Kristo 展示使用具有状态的函子执行此操作的 STLish 方式...尽管您不应该使用前导下划线来标​​记局部变量,因为这些名称是为编译器保留的,int当前的_;具有相同的影响,并且保证不会发生冲突。【参考方案9】:

Thomas Kammeyer 答案的可能实现

我发现 Thomas 的方法非常聪明和有用 - 因为我们中的一些人梦想着代码,而我发现实际实现有点棘手,我想提供一些现成的代码。

这里提出的解决方案尽可能通用:

除了迭代器必须满足 ValueSwappable 和 RandomAccessIterator 的要求(由于使用 nth_element 进行部分排序)之外,不对容器或范围的类型做出任何假设 可以使用任何数字类型 - 所需的特征如下所述

我认为的另一个改进是可以及早检查无间隙条件:因为无论如何我们都必须扫描最小值,我们也可以同时扫描最大值,然后确定数字范围是否甚至包含间隙值得寻找。

最后但并非最不重要的一点是,相同的递归方法可以适用于排序范围!如果您在模板值参数中对范围是否已排序进行编码,则可以简单地跳过部分排序,并让确定最小/最大元素成为无操作。

#include <type_traits>
#include <iterator>
#include <tuple>
#include <utility>
#include <algorithm>
#include <cstddef>

// number type must be:
// * arithmetic
//   * subtractable (a - b)
//   * divisible by 2 (a / 2)
// * incrementable (++a)
// * less-than-comparable (a < b)
// * default-constructible (A)
// * copy-constructible
// * value-constructible (A(n))
// * unsigned or number range must only contain values >0

/** Find lowest gap value in a range */
template<typename Range>
typename std::remove_reference_t<Range>::value_type
lowest_gap_value_unsorted(Range&& r)

    static_assert(!std::is_lvalue_reference_v<Range> && !std::is_const_v<Range>, "lowest_gap_value_unsorted requires a modifiable copy of the passed range");

    return lowest_gap_value_unsorted(std::begin(r), std::end(r), std::size(r));


/** Find lowest gap value in a range with specified size */
template<typename Range>
typename std::remove_reference_t<Range>::value_type
lowest_gap_value_unsorted(Range&& r, std::size_t N)

    static_assert(!std::is_lvalue_reference_v<Range> && !std::is_const_v<Range>, "lowest_gap_value_unsorted requires a modifiable copy of the passed range");

    return lowest_gap_value_unsorted(std::begin(r), std::end(r), N);


/** Find lowest gap value in an iterator range */
template<typename Iterator>
typename std::iterator_traits<Iterator>::value_type
lowest_gap_value_unsorted(Iterator first, Iterator last)

    return lowest_gap_value_unsorted(first, last, std::distance(first, last));


/** Find lowest gap value in an iterator range with specified size */

template<typename Iterator>
typename std::iterator_traits<Iterator>::value_type
lowest_gap_value(Iterator first, Iterator last, std::size_t N)

    typedef typename std::iterator_traits<Iterator>::value_type Number;

    if (bool empty = last == first)
        return increment(Number);

    Iterator minElem, maxElem;
    std::tie(minElem, maxElem) = std::minmax_element(first, last);

    if (bool contains0 = !(Number < *minElem))
        throw std::logic_error("Number range must not contain 0");

    if (bool missing1st = increment(Number) < *minElem)
        return increment(Number);

    if (bool containsNoGap = !(Number(N) < increment(*maxElem - *minElem)))
        return increment(*maxElem);

    return lowest_gap_value_unsorted_recursive(first, last, N, *minElem);


template<typename Iterator>
typename std::iterator_traits<Iterator>::value_type
lowest_gap_value_unsorted_recursive(Iterator first, Iterator last, std::size_t N, typename std::iterator_traits<Iterator>::value_type minValue)

    typedef typename std::iterator_traits<Iterator>::value_type Number;

    if (N == 1)
        return ++minValue;
    if (N == 2)
    
        // determine greater of the 2 remaining elements
        Number maxValue = !(minValue < *first) ? *std::next(first) : *first;
        if (bool gap = ++minValue < maxValue)
            return minValue;
        else
            return ++maxValue;
    

    Iterator medianElem = std::next(first, N / 2);
    // sort partially
    std::nth_element(first, medianElem, last);

    if (bool gapInLowerHalf = (Number(N) / 2 < *medianElem - minValue))
        return lowest_gap_value_unsorted_recursive(first, medianElem, N / 2, minValue);
    else
        return lowest_gap_value_unsorted_recursive(medianElem, last, N / 2 + N % 2, *medianElem);
;

template<typename T>
T increment(T v)

    return ++v;

【讨论】:

以上是关于查找数字序列中的空白的主要内容,如果未能解决你的问题,请参考以下文章

神经网络,用于查找数字序列中的模式

从序列中为配置单元中的每个值查找缺失的数字

如何在 SQL 中查找范围内的空白

strtol 等人的规范中的混淆语言

刷题之路(八)求字符串中的符合要求的数字

SQL Server 填补时间序列中的空白