在向量中找到最长的“连续数字”条纹的最快方法是啥?

Posted

技术标签:

【中文标题】在向量中找到最长的“连续数字”条纹的最快方法是啥?【英文标题】:What is the fastest way to find longest 'consecutive numbers' streak in vector ?在向量中找到最长的“连续数字”条纹的最快方法是什么? 【发布时间】:2012-07-25 15:00:24 【问题描述】:

我有一个排序的std::vector<int>,我想在这个向量中找到最长的“连续数字条纹”,然后返回它的长度和条纹中的最小数字。

为您形象化: 假设我们有: 1 3 4 5 6 8 9

我希望它返回:maxStreakLength = 4streakBase = 3

有时可能会有 2 条条纹,我们必须选择哪条更长。

最好(最快)的方法是什么?我试图实现这一点,但我在处理向量中的多个条纹时遇到了问题。我应该使用临时向量然后比较它们的长度吗?

【问题讨论】:

对此有一个易于实现的 O(n) 解决方案,它不需要额外的向量。不过,我很确定还有一种更快的方法,即分而治之的策略。可以重复数字吗? @MarkRansom 还有?就这样吧。我不担心人为的问题,这个相当简单的问题仍然比最实际相关的“如何让 jQuery 使用 Facebook 将 OpenGL 存储到 SQL”问题具有更多的算法价值。 @ChristianRau,这样的问题的目的是让你学习一些东西或证明你已经知道它。在这种情况下为他们考虑别人的想法会适得其反,至少对那个人来说是这样。 @Lie Ryan:输入数组已排序这一事实立即意味着求解器应该利用它。线性解决方案没有。我刚刚发布了一个 D&C 解决方案。也许更聪明的事情是可能的。 @Patryk:你事先知道数组中的所有数字都是唯一的吗? 【参考方案1】:

不,您可以一次通过向量执行此操作,并且只存储迄今为止找到的最长起点和长度。您还需要比“N”个比较少得多。 *

提示:如果您已经说在第 5 位 (=6) 结束的 4 长比赛,那么您接下来必须检查哪个位置?

[*] 留给读者作为练习,以计算出可能的 O( ) 复杂度;-)

【讨论】:

如果您知道您的第 5 位和第 6 位不同,那么我认为您应该检查第 6 位和第 7 位之间的差异,因为可能会有新的连胜开始。 (除非您想将vector.size() 考虑在内,否则您可能会忽略这一点,因为在此vector 中不会再出现更长的连胜记录 - 但您仍然会与vector.size() 和剩余条目数进行比较) . 如果你已经得到了一个 length=4 的结果,那么你只对 5 次或更多的运行感兴趣。因此,当您在位置 6 中找到“8”时,您应该检查位置 6+5 以查看值是否为 13 (=8+5)。如果没有,则无需检查之间的各个值 @Martin:但您仍然需要查看值之间的值。例如,您仍然需要检查是否从位置 7 开始连续。 @KeithRandall - 是的,但你会从第 11 位开始向下检查,如果之前的位置不是 X-1,那么干预就不可能是连胜。随着当前最好的时间越来越长,您可以跳过的数量也越来越大。【参考方案2】:

看看是否可以利用数组已排序的事实来改进算法会很有趣。首先想到的是:如果你知道输入数组中的所有数字都是唯一的,那么对于数组中的元素[i, j]的范围,你可以立即判断元素是否在该范围是否连续,无需实际查看范围。如果这个关系成立

array[j] - array[i]  ==  j - i

那么您可以立即说该范围内的元素是连续的。显然,这个标准使用了数组已排序且数字不重复这一事实。

现在,我们只需要开发一种利用该标准的算法。这是一种可能的递归方法:

    递归步的输入是元素[i, j]的范围。最初是 [0, n-1] - 整个数组。 将上述条件应用于范围[i, j]。如果范围是连续的,则无需进一步细分。将范围发送到输出(有关详细信息,请参见下文)。 否则(如果范围不连续),将其分成两等份[i, m][m+1, j]。 递归调用下半部分 ([i, m]) 和上半部分 ([m+1, j]) 的算法。

上述算法将使用左优先方法执行数组的二进制分区和分区树的递归下降。这意味着该算法将以从左到右的顺序找到具有连续元素的相邻子范围。您需要做的就是将相邻的子范围连接在一起。当您收到在步骤 2 中“发送到输出”的子范围 [i, j] 时,如果它们确实是连续的,则必须将其与先前收到的子范围连接起来。或者你必须开始一个新的范围,如果它们不是连续的。您一直在跟踪迄今为止发现的“最长连续范围”。

就是这样。

这种算法的好处是它“早期”检测到连续元素的子范围,而不需要查看这些子范围的内部。显然,最坏情况下的性能(如果根本没有连续的子范围)仍然是O(n)。在最好的情况下,当整个输入数组是连续的时,这个算法会立即检测到它。 (我仍在为此算法进行有意义的 O 估计。)

该算法的可用性再次受到唯一性要求的破坏。我不知道在你的情况下这是否是“给定的”。

无论如何,这是一个可能的 C++ 实现

typedef std::vector<int> vint;
typedef std::pair<vint::size_type, vint::size_type> range;

class longest_sequence

public:
  const range& operator ()(const vint &v)
   
    current = max = range(0, 0);

    process_subrange(v, 0, v.size() - 1);
    check_record();

    return max;
  

private:
  range current, max;

  void process_subrange(const vint &v, vint::size_type i, vint::size_type j);
  void check_record();
;

void longest_sequence::process_subrange(const vint &v, 
                                        vint::size_type i, vint::size_type j)

  assert(i <= j && v[i] <= v[j]);
  assert(i == 0 || i == current.second + 1);

  if (v[j] - v[i] == j - i)
   // Consecutive subrange found
    assert(v[current.second] <= v[i]);
    if (i == 0 || v[i] == v[current.second] + 1)
      // Append to the current range
      current.second = j;
    else
     // Range finished
      // Check against the record 
      check_record();
      // Start a new range
      current = range(i, j);
    
  
  else
   // Subdivision and recursive calls
    assert(i < j);
    vint::size_type m = (i + j) / 2;
    process_subrange(v, i, m);
    process_subrange(v, m + 1, j);
  


void longest_sequence::check_record()

  assert(current.second >= current.first);
  if (current.second - current.first > max.second - max.first)
    // We have a new record
    max = current;


int main()

  int a[] =  1, 3, 4, 5, 6, 8, 9 ;
  std::vector<int> v(a, a + sizeof a / sizeof *a);
  range r = longest_sequence()(v);
  return 0;

【讨论】:

反例:[2,2,2,4,4,4]。它不包含连续的元素,但它满足array[5] - array[0] &lt;= 5 - 0 @AndreyT - 我关于连续数字的帖子不知何故消失了,但我试图与 Keith 表达同样的观点 @Keith Randall, Martin Beckett:你说得对,它根本不起作用,除非我们事先知道输入数组中的所有数字不仅是排序的,而且是唯一的。我的错。 Brainfart...感谢您指出。我在答案中添加了唯一性要求。【参考方案3】:

我认为应该这样做?

size_t beginStreak = 0;
size_t streakLen = 1;
size_t longest = 0;
size_t longestStart = 0;
for (size_t i=1; i < len.size(); i++) 
    if (vec[i] == vec[i-1] + 1) 
        streakLen++;
    
    else 
        if (streakLen > longest) 
            longest = streakLen;
            longestStart = beginStreak;
        
        beginStreak = i;
        streakLen = 1;
    

if (streakLen > longest) 
    longest = streakLen;
    longestStart = beginStreak;

【讨论】:

【参考方案4】:

您无法在O(N) 时间内解决此问题。想象一下,您的列表是第一个 N-1 偶数,加上一个奇数(从第一个 N-1 奇数中选择)。然后在列表中的某处有一条长度为 3 的单条,但最坏的情况是您需要扫描整个列表才能找到它。即使平均而言,您也需要检查至少一半的列表才能找到它。

【讨论】:

【参考方案5】:

类似于 Rodrigo 的解决方案,但也解决了您的示例:

#include <vector>
#include <cstdio>

#define len(x) sizeof(x) / sizeof(x[0])

using namespace std;

int nums[] = 1,3,4,5,6,8,9;
int streakBase = nums[0];
int maxStreakLength = 1;

void updateStreak(int currentStreakLength, int currentStreakBase) 
  if (currentStreakLength > maxStreakLength) 
    maxStreakLength = currentStreakLength;
    streakBase = currentStreakBase;
  


int main(void) 
  vector<int> v;
  for(size_t i=0; i < len(nums); ++i)
    v.push_back(nums[i]);

  int lastBase = v[0], currentStreakBase = v[0], currentStreakLength = 1;

  for(size_t i=1; i < v.size(); ++i) 
    if (v[i] == lastBase + 1) 
      currentStreakLength++;
      lastBase = v[i];
     else 
      updateStreak(currentStreakLength, currentStreakBase);
      currentStreakBase = v[i];
      lastBase = v[i];
      currentStreakLength = 1;
    
  
  updateStreak(currentStreakLength, currentStreakBase);
  printf("maxStreakLength = %d and streakBase = %d\n", maxStreakLength, streakBase);

  return 0;

【讨论】:

以上是关于在向量中找到最长的“连续数字”条纹的最快方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 python 中查找特征值/向量的最快方法是啥?

计算混合实复矩阵向量积的最快方法是啥?

在c ++中不同行或列旁边的矩阵中搜索最小值和最大值的最快方法是啥

在C中找到整数中最高设置位(msb)的最快/最有效方法是啥?

在C中找到整数中最高设置位(msb)的最快/最有效方法是啥?

从 Numpy 中的 N 个向量中找到所有唯一的(几乎)平行 3d 向量对的最快方法