我最近偶然发现了一个问题。
我正在处理的算法的一部分需要在排序的数字列表中找到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个连续的最小组。
我最终拼凑了一些完成这项工作的垃圾代码,但我想知道一些经验丰富的程序员会如何做到这一点。利用尽可能多的C ++ 11样式代码advertised by Stroustroup himself并使用尽可能多的C ++ 11 STL来获得推理的正确性,可移植性和紧凑性。
答案 0 :(得分:2)
在算法方面,有一个有趣的优化;伪代码:
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)
,因此我们只需要查找其初始元素(二进制搜索)。
注意:由于二进制搜索的“复杂性”增加而不是线性搜索(常数因子),并且由于相当不可预测的内存访问,对于小范围列表而言,这可能会比严格的线性方法。
答案 1 :(得分:2)
如果速度无关紧要:
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();
}
答案 2 :(得分:1)
这是我的解决方案。它没有使用任何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;&lt; cur_value_index&lt;&lt; ENDL; }
我假设序列是在数组a
中提供的,而N
是您在问题中谈论的限制。
我使用了矢量进行说明,但是如果我们没有随机访问例如列表,则可以应用相同的算法。在这种情况下,我们将迭代器保留在序列的元素而不是索引,但其余的将保持不变。
答案 3 :(得分:1)
这里的重要内容取决于插入和删除与搜索的频率,您正在查看的列表数量等等。
目前,我将做两个假设:
如果这是真的,你可以通过对输入数据进行行程编码来开始,这样就可以得到值/计数对。
然后,您主要根据计数对这些对进行排序,其次是对值进行排序。最后,使用std::lower_bound
查找值,仅根据计数进行比较。
这需要O(N log N)进行预处理。作为交换,每个查询都需要O(log N)而不是O(N)。因此,您需要对预处理数据执行O(N)查询以证明预处理的合理性。
答案 4 :(得分:1)
#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;
}
答案 5 :(得分:1)
当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循环开始时获得一个新值。