在一个排序的列表中正好找到N个连续项
Finding exactly N consecutives in a sorted list
我最近偶然发现了一个小问题。
我正在研究的算法的一部分需要在一个排序的数字列表中找到n
连续的数字。
因此,例如,列表看起来像这样:
1 2 2 3 4 5 5 5 6 7 8 9 9 9 9
给定该列表和N,即连续重复的数量,算法需要在恰好为N个连续数字的最小组中找到第一个数字。例如,对于N=2和给定的列表,算法应该找到"2"。当N=3时,它应该通过2的组,而不是5的组,因为它是该列表中最小的3个连续重复的组。它不应该返回9,因为实际上有4个连续的9,并且在N=3的情况下,我们正在寻找恰好连续3个的最小组。
最后,我确实拼凑出了一些做这项工作的代码,但我想知道一些有经验的程序员会怎么做。利用Stroustroup自己宣传的C++11风格的代码,并使用C++11 STL来实现推理的正确性、可移植性和紧凑性。
如果速度无关紧要:
template <class T >
T firstOfN( std::vector<T> list, unsigned N ){
std::multiset<T> mset( list.begin(), list.end() );
for( typename std::multiset<T>::iterator it = mset.begin(); it != mset.end(); ++it ){
if( mset.count( *it ) == N ) return *it;
}
throw std::exception();
}
在算法方面,有一个有趣的优化;伪代码:
size_t N;
RaIterator cur = myvector.begin(), end = myvector.end();
while(cur < end-(N-1))
{
if(*cur == *(cur+N))
{
if(cur+N == end || *cur != *(cur+N+1))
return {cur, cur+N};
else
cur = upper_bound(cur+N+1, end, *cur);
}else
{
cur = lower_bound(cur, cur+N, *(cur+N));
}
}
return {end, end};
如果我们有随机访问迭代器,一旦我们有了初始元素(前面的元素较小,后面的元素较大或相等),我们就可以很快跳过范围:
如果是
*cur == *(cur+N)
,则具有值*cur
的范围足够大。如果是*cur != *(cur+N+1)
或cur+N == end
,那么它确实是我们要寻找的范围。否则,它太大了,我们可以搜索下一个范围(线性搜索或[cur+N+1, end)
中的二进制搜索)。否则,
*cur != *(cur+N)
,则电流范围太小。完全在[cur, cur+N]
内部的每个范围也太小,所以下一个要检查的范围是从[cur, cur+N]
内部开始并延伸到cur+N
之外的范围。这个范围的值为*(cur+N)
,所以我们只需要找到它的初始元素(二进制搜索)。
注意:与线性搜索(常数因子)相比,二进制搜索的"复杂性"增加了,并且由于相当不可预测的内存访问,对于小范围列表,这可能比严格的线性方法慢。
这里很大程度上取决于插入和删除的频率与搜索、您正在查看的列表的大小等。
目前,我要做两个假设:
- 你要处理的列表足够大,渐近更好的算法可能会战胜明显的线性搜索
- 您正在使用基本上是静态的数据进行大量查询
如果这是真的,那么首先对输入数据进行游程编码,这样就可以得到值/计数对。
然后主要根据计数对这些对进行排序,其次根据值对它们进行排序。最后,使用std::lower_bound
查找一个值,比较仅基于计数。
这需要O(N log N)进行预处理。作为交换,每个查询都需要O(log N)而不是O(N)。因此,您需要对经过预处理的数据进行O(N)查询,以证明预处理的合理性。
当N较大时,对N个相同数字的检测可能会"优化"一点。
for (int i = 0; i < n - N + 1; ) {
int ai = a[i]; // New value
if (ai == a[i + N - 1]) { // Last element same
if (i + N >= n || ai != a[i + N]) { // Thereafter not
return i;
}
i += N; // Move to last known same element (or past end)
}
// Go to next new value:
++i;
while (i < n - N + 1 && a[i] == ai) {
++i;
}
}
它依赖于在for循环的开头有一个新值。
这是我的解决方案。它不使用任何stl标准算法,但它具有尽可能好的复杂性-O(n),我相信它是可读和可理解的:
unsigned cur_value_index = 0;
unsigned range_size = 1;
for (unsigned i = 1; i < a.size(); ++i) {
if (a[i] == a[cur_value_index]) {
range_size++;
} else {
if (range_size == N) {
cout << cur_value_index << endl;
break;
}
cur_value_index = i;
range_size = 1;
}
}
if(range_size==N){cout<lt;cur_value_index<lt;endl;}
我假设序列是在数组a
中提供的,而N
是您在问题中谈到的极限。
我用矢量来举例说明,但如果我们没有随机访问,例如列表,同样的算法也可以应用。在这种情况下,我们将保留序列元素的迭代器,而不是索引,但其余部分将保持不变。
#include <algorithm>
#include <array>
#include <iostream>
using namespace std;
template<class T>
class Sequence
{
public:
Sequence(const uint32_t num_items);
~Sequence(){}
bool operator()(const T data);
private:
T m_value;
uint32_t m_counter;
uint32_t m_max;
};
template<class T>
Sequence<T>::Sequence(const uint32_t num_items)
: m_value(0),
m_counter(0),
m_max(num_items)
{
}
template<class T>
bool Sequence<T>::operator()(const T data)
{
if(m_value == data) {
m_counter++;
} else if(m_counter == m_max{
m_value = data;
m_counter = 0;
return true;
} else{
m_value = data;
m_counter = 0;
}
return false;
}
int main()
{
int data[] = {1,2,2,3,4,5,5,5,6,7,8,9,9,9,9};
array<int,15> ar;
for(uint32_t i = 0; i < 15; i++)
ar[i] = data[i];
//find three consecutive numbers
Sequence<int> seq(3);
//getting the first occurence of the sequence
array<int,15>::iterator it = find_if(ar.begin(),ar.end(),seq);
//printing the iterator position from begin
cout << distance(ar.begin(),it) << endl;
return 0;
}
- Pybind11:将元组列表从Python传递到C++
- 从链接列表c++中删除一个项目
- 如何(从固定列表中)选择一个数字序列,该序列将与目标数字相加
- C++如何通过用户输入删除列表元素
- 读取文件的最后一行并输入到链接列表时出错
- 复制列表初始化的隐式转换的等级是多少
- 模板元程序查找相似的连续类型名称
- LNK2038、MSVS2017 MAGMA的原因列表
- 不能在初始值设定项列表中将非常量表达式从类型 'int' 缩小到'unsigned long long'
- 没有为自己的结构调用列表推回方法
- 使用简单类型列表实现的指数编译时间.为什么
- 一对向量构造函数:初始值设定项列表与显式构造
- 标准是否使用多余的大括号(例如 T{{{10}}})定义列表初始化?
- 通过for循环使用用户输入填充列表
- 通过在带有 C++ 的列表中添加连续元素来计算新的整数列表
- 如何比较C 列表中的两个连续元素
- 在一个排序的列表中正好找到N个连续项
- 动态规划:计算连续跳跃列表的所有可能的结束位置
- 计算(列表中)连续元素的最大和
- 使用连续的无符号整数列表初始化 std::vector<无符号 int>