C++:查找子数组数组中的最大整数

C++: Find the maximum integer in an array of sub-arrays

本文关键字:数组 整数 查找 C++      更新时间:2023-10-16

我遇到了一个问题,我想编写一个算法,该算法可以返回较大数组中k个元素的每个连续子数组的最大元素,并将这些max元素读入自己的数组中,如下所示:

Given int array = {3, 7, 20, 6, 12, 2, 0, 99, 5, 16}, and int k = 4,
--> creates the array {20, 20, 20, 12, 99, 99, 99} 
[because there are 7 consecutive sub-arrays of size 4 within the given array:
{3, 7, 20, 6}, {7, 20, 6, 12}, {20, 6, 12, 2}, ... , {0, 99, 5, 16}
and the max element of these, respectively, is 20, 20, 20, ..., 99 which 
are read into the resulting array. 

现在这是我的问题:我知道如何在 O(n^2( 复杂性中实现它,但想让它更快,以便它可以是 O(n(,或者如果不可能,则为 O(nlog(n((。有谁知道是否有更快的方法来做到这一点,如果有,如何?

首先,朴素算法的复杂性是 O(k(n-k+1(((通常这近似于 O(k.n((,而不是 O(n^2(。 这就是,对于每个连续的子数组(可能为 n-k+1(,您必须执行 k 比较。

你可以通过一些记忆做得更好,使用一个额外的长度 k 数组,我们可以称之为 maximums . 该数组将存储下一个最大值的索引。

对于数据集的每次迭代,您检查maximums的第一个元素。 您删除任何"过期"索引,现在第一个元素是当前迭代的答案。

当您在数据上滑动窗口(大小 k(时,将当前索引推到maximums 上,然后按如下方式修剪它:索引 maximums[i] 处的值必须小于索引 maximums[i-1] 处的值。 如果不是,那么您继续在maximums的开头冒泡索引,一次一个点,直到这成为真的。

实际上,最好将maximums数组视为环形缓冲区。 修剪过程会将尾巴缩小回头部,而弹出任何"过期"的最大值(当窗口滑过它们时(会将头部向前推进一步。

这有点笨拙,但这里有一些工作代码来说明:

#include <vector>
#include <iostream>
int main()
{
    const int window_size = 4;
    std::vector<int> vals = { 3, 7, 20, 6, 12, 2, 0, 99, 5, 16 };
    std::vector<int> maximums( window_size );
    int mhead = 0, mtail = 0;
    for( int i = 1; i < vals.size(); i ++ )
    {
        // Clean out expired maximum.
        if( maximums[mhead] + window_size <= i )
        {
            int next_mhead = (mhead + 1) % window_size;
            if( mtail == mhead ) mtail = next_mhead;
            mhead = next_mhead;
        }
        if( vals[i] >= vals[ maximums[mtail] ] )
        {
            // Replace and bubble up a new maximum value.
            maximums[mtail] = i;
            while( mhead != mtail && vals[ maximums[mtail] ] >= vals[ maximums[(mtail+window_size-1)%window_size] ] )
            {
                int prev_mtail = (mtail + window_size - 1) % window_size;
                maximums[prev_mtail] = maximums[mtail];
                mtail = prev_mtail;
            }
        }
        else
        {
            // Add a new non-maximum.
            mtail = (mtail + 1) % window_size;
            maximums[mtail] = i;
        }
        // Output current maximum.
        if( i >= window_size - 1 )
        {
            std::cout << vals[ maximums[mhead] ] << " ";
        }
    }
    std::cout << std::endl;
    return 0;
}

现在,时间复杂度...

最好的情况是 O(n(,如果所有数据都已排序(升序或降序(,就会发生这种情况。

我相信,最坏的情况是O(2n(。 在一次迭代中需要 k 个额外操作的唯一方法是,如果您已经有 k 个线性复杂度的步骤(以便环形缓冲区已满(。 在这种情况下,环形缓冲区将在下一步中为空。 由于我们只能填充和清空环形缓冲区 n/k 次,因此这些偶尔的 k 操作以 k.n/k 或仅 n 出现。

您应该能够证明,即使环形缓冲区的持续部分清空也会导致同样的复杂性。

最后,我们可以总结并称整个事情为O(n(,因为任何常数因子对于大n都变得微不足道。 它实际上比我预期的要好。=)