查找数字序列中的空白
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());
【讨论】:
Youinsequence
op() 中的测试过于复杂。你可以写得更简单更清楚:insequence = insequence && x == _current;
。一般来说,bool1 ? bool2 : bool3
总是可以写成bool1 && 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;
【讨论】:
以上是关于查找数字序列中的空白的主要内容,如果未能解决你的问题,请参考以下文章