如何在给定数组中找到某个固定给定长度的每个子数组的最大值

时间:2011-08-15 14:11:47

标签: c++ arrays algorithm

我们给出了一个n个元素和一个整数k的数组。假设我们想在数组中滑动长度为k的窗口,报告每个窗口中包含的最大值。例如,给定数组

15  10   9  16  20  14  13

给定一个长度为4的窗口,我们将输出

[15  10   9  16] 20  14  13   ---> Output 16
 15 [10   9  16  20] 14  13   ---> Output 20
 15  10 [ 9  16  20  14]  13  ---> Output 20
 15  10   9 [16  20  14  13]  ---> Output 20

所以结果将是

16  20  20  20

我通过跟踪每个点的窗口的最大元素来解决问题,但是当最大的元素从窗口滑出时遇到了问题。那时,我想不出一个快速的方法来弄清楚剩下的最大元素是什么。

有谁知道解决这个问题的有效算法?

6 个答案:

答案 0 :(得分:11)

This older question 讨论了如何在O(1)时间内构建支持insert,dequeue和find-min的队列数据结构。请注意,这是标准优先级队列,而是一个队列,在任何时候您都可以在O(1)时间内找到它包含的最小元素的值。您可以轻松修改此结构以支持O(1)中的find-max而不是find-min,因为这与此特定问题更相关。

使用此结构,您可以在O(n)时间内解决此问题,如下所示:

  1. 将数组的前k个元素排入特殊队列。
  2. 对于数组其余部分中的每个元素:
    1. 使用队列的find-max操作报告当前子范围的最大元素。
    2. 从队列中取出一个元素,留下旧范围的最后一个k-1元素。
    3. 从序列中排队下一个元素,使队列现在保持序列的下一个k元素子范围。
  3. 这需要总共O(n)次,因为您访问每个数组元素一次,每次最多入队和出列一次,并且将find-max调用恰好n-k次。我认为这很酷,因为复杂性独立于k ,最初似乎不一定是可能的。

    希望这有帮助!谢谢你提出一个很酷的问题!

答案 1 :(得分:1)

您可以保留当前元素的二进制搜索树,例如,将它们保存为值出现对。除此之外,你的滑动窗口算法应该足够好了。

这样,选择最大值(子节中的最大元素)将花费O(logL)时间,L是当前子节的长度;添加新也将是O(logL)。要删除最旧的一个,只需搜索该值并将计数减1,如果计数变为0则删除它。

因此总时间为O(NlogL),N为数组的长度。

答案 2 :(得分:1)

您可以像禁忌搜索一样继续:

遍历列表并获得第4个第i个元素的最大值。 然后在下一步中检查第i + 1个元素是否优于前一个元素的最大值

  • if i + 1> = previous max then new max = i + 1 reinialise tabu
  • 如果i + 1<先前的最大值,如果发现之前的最大值小于N. 前一步(这里是禁忌)
  • 如果i + 1< preuous max和之前的max是tabu然后采取新的 最大的4 i + 1个元素。

我不确定它是否清楚,但请告诉我你是否有任何疑问。 下面是python中的一个代码来测试它。

l=[15,10,9,16,20,14,13,11,12]
N=4
res=[-1] #initialise res
tabu=1  #initialise tabu
for k in range(0,len(l)):
#if the previous element res[-1] is higher than l[k] and not tabu then keep it
#if the previous is tabu and higher than l[k] make a new search without it
#if the previous is smaller than l[k] take the new max =l[k]

    if l[k]<res[-1] and tabu<N:
        tabu+=1
        res.append(res[-1])
    elif l[k] < res[-1] and tabu == N:
         newMax=max(l[k-N+1:k+1])
         res.append(newMax)
         tabu=N-l[k-N+1:k+1].index(newMax) #the tabu is initialized depending on the position of the newmaximum
    elif l[k] >= res[-1]:
        tabu=1
        res.append(l[k])

print res[N:] #take of the N first element

<强>复杂度:

我将代码thx更新为flolo和复杂性。它不再是O(N)而是O(M * N)

最糟糕的情况是,您需要在循环的每个步骤重新计算最大值。例如,我是一个严格减少的名单。

在循环的每个步骤中,您需要重新计算M个元素的最大值

然后总体复杂度为O(M * N)

答案 3 :(得分:1)

我能想到的最好的是O(n log m)。 你可以通过动态编程来实现它。

在第一遍中,您会发现每个元素的最大值是元素本身和下一个元素的最大值。

现在你有n个最大值(窗口大小= 2)。

现在你可以在这个数组中找到每个元素的最大值和这个数组中的overnext(给出每个元素下一个4的最大值,即窗口大小= 4)。

然后你可以再次这样做(并且每次窗口大小翻倍)。

正如人们清楚地看到窗口大小呈指数级增长。

因此运行时为O(n log m)。实现有点棘手,因为你必须考虑角落和特殊情况(特别是当窗口大小不应该是2的幂),但它们不会影响渐近运行时。

答案 4 :(得分:1)

使用Double-ended queue可以实现O(n)复杂度。

这是C#实现

    public static void printKMax(int[] arr, int n, int k)
    {
        Deque<int> qi = new Deque<int>();
        int i;
        for (i=0;i< k; i++) // first window of the array
        {
            while ((qi.Count > 0) && (arr[i] >= arr[qi.PeekBack()]))
            {
                qi.PopBack();
            }
            qi.PushBack(i);
        }

        for(i=k ;i< n; ++i)
        {
            Console.WriteLine(arr[qi.PeekFront()]); // the front item is the largest element in previous window.
            while (qi.Count > 0 && qi.PeekFront() <= i - k) // this is where the comparison is happening!
            {
                qi.PopFront(); //now it's out of its window k 
            }
            while(qi.Count>0 && arr[i]>=arr[qi.PeekBack()]) // repeat
            {
                qi.PopBack();
            }
            qi.PushBack(i);
        }

        Console.WriteLine(arr[qi.PeekFront()]);
    }

答案 5 :(得分:0)

请查看我的代码。据我所知,我认为这个算法的时间复杂度是

  

O(l)+ O(n)

    for (int i = 0; i< l;i++){
        oldHighest += arraylist[i];
    }
    int kr = FindMaxSumSubArray(arraylist, startIndex, lastIndex);



    public static int FindMaxSumSubArray(int[] arraylist, int startIndex, int lastIndex){

    int k = (startIndex + lastIndex)/2;
    k = k - startIndex;
    lastIndex = lastIndex  - startIndex;

    if(arraylist.length == 1){
        if(lcount<l){
            highestSum += arraylist[0];
            lcount++;
        }
        else if (lcount == l){
            if(highestSum >= oldHighest){
                oldHighest = highestSum;
                result = count - l + 1;
            }
            highestSum = 0;
            highestSum += arraylist[0];
            lcount = 1;
        }
        count++;
        return result;
    }

    FindMaxSumSubArray(Arrays.copyOfRange(arraylist, 0, k+1), 0, k);
    FindMaxSumSubArray(Arrays.copyOfRange(arraylist, k+1, lastIndex+1), k+1, lastIndex);

    return result;
 }

我不明白在递归时是做得更好还是线性做?