在排序列表中准确找到N个连续符号

时间:2013-11-27 19:59:49

标签: c++ algorithm sorting c++11

我最近偶然发现了一个问题。

我正在处理的算法的一部分需要在排序的数字列表中找到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来获得推理的正确性,可移植性和紧凑性。

6 个答案:

答案 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)

这里的重要内容取决于插入和删除与搜索的频率,您正在查看的列表数量等等。

目前,我将做两个假设:

  1. 你正在处理足够大的列表,渐近更好的算法可能会胜过明显的线性搜索。
  2. 您正在使用基本上静态的数据进行大量查询。
  3. 如果这是真的,你可以通过对输入数据进行行程编码来开始,这样就可以得到值/计数对。

    然后,您主要根据计数对这些对进行排序,其次是对值进行排序。最后,使用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循环开始时获得一个新值。