【问题标题】:How to find maximum of each subarray of some fixed given length in a given array如何在给定数组中找到某个固定给定长度的每个子数组的最大值
【发布时间】:2011-10-27 07:30:30
【问题描述】:

给定一个包含 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

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

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

【问题讨论】:

  • 现在的问题是,您对此有何想法?
  • @Als:我整个晚上都在想它,我就像一个正在移动的子数组长度的窗口一样接近,我们需要从中找到最大值。它工作得非常好。然后我陷入了当前最大元素超出“滑动窗口”的问题。那时我需要存储滑动窗口的所有当前元素。 :(
  • mohan:请随意在 Q 中表达您的想法,它会告诉读者,您尝试过什么,否则他们只会觉得您只是在这里问 Q。
  • 提示:动态编程 (en.wikipedia.org/wiki/Dynamic_programming) 我认为可以让您解决 O(M*N) 中的问题,其中 M 是数组的长度,N 是数组中的元素数。
  • @Matthieu:O(MN) 不是“朴素”算法的复杂性,即简单的 2D 循环吗?

标签: c++ arrays algorithm


【解决方案1】:

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

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

  1. 将数组的前 k 个元素排入特殊队列。
  2. 对于数组其余部分中的每个元素:
    1. 使用队列的 find-max 操作报告当前子范围的最大元素。
    2. 从队列中取出一个元素,保留旧范围的最后 k-1 个元素。
    3. 将序列中的下一个元素排入队列,导致队列现在保存序列的下一个 k 元素子范围。

这总共需要 O(n) 时间,因为您访问每个数组元素一次,最多将每个元素入队和出队一次,并且调用 find-max 恰好 n-k 次。我认为这很酷,因为复杂性 独立于 k,这最初看起来并不一定是可能的。

希望这会有所帮助!感谢您提出一个很酷的问题!

【讨论】:

  • 这不起作用。我检查了它,建议的方法是错误的。 a) 具有 min 属性的堆栈和 b) 来自 2 个堆栈的队列的链接解决方案各自工作,但两者结合起来都不起作用。不知道为什么没有人意识到,但这是错误的。问题是两个堆栈都可以保持正确的最小值,但不可能在两个堆栈上保持一致。试试这个例子:Push 4 Values with min/max。弹出一个,所有 4 个都被复制,结果还可以。现在再push一次再pop,新push的值不考虑。
  • @flolo- 你能详细说明一下吗?只要您查看两个堆栈的最小元素,我很确定答案确实有效。具体出了什么问题?另外,我发布了对该问题的答案,该答案还实现了由不同结构(笛卡尔树)支持的最小队列,所以即使第一个答案是错误的,我相信我发布的内容仍然正确地实现了抽象。我现在就调查第一个答案。
  • @flolo- 另外,虽然我理解你为什么不赞成这个,但你能删除反对票吗?您仍然没有排除我在上一个问题中提出的 min-queue 的替代实现,并且从听起来我认为您可能错误实现了两栈解决方案(您是否查看了 min 元素两个堆栈?)
  • @flolo- 我刚刚在我的计算机上实现了两栈解决方案,它工作得非常好。唯一棘手的部分是您必须记住查看两个堆栈的分钟数。正如您所指出的那样,孤立地查看一个并不能正常工作。
  • 哦,也许你是对的,结合两个堆栈的最大值。我尝试(在这一点上的答案不是很明确)将完整的对从堆栈 1 复制到堆栈 2。如果您在堆栈 2 中再次创建最大值并始终查看两个堆栈并将它们最大化,它似乎可以工作:非常很好的解决方案。
【解决方案2】:

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

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

所以总时间将是 O(NlogL),N 是数组的长度。

【讨论】:

  • 但是从最大堆中,你只能提取最大值而不是最旧的值....我想你明白我想说的了。
【解决方案3】:

我能快速想出的最佳值是 O(n log m)。 你可以通过动态规划来实现。

在第一遍中,您会找到每个元素的最大值,即元素本身和下一个元素的最大值。

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

现在您可以在此数组中找到每个元素的最大值以及该数组中的 overnext(为每个元素提供下一个 4 的最大值,即窗口大小 = 4)。

然后你可以再做一次,再一次(每次窗口大小翻倍)。

正如人们清楚地看到的那样,窗口大小呈指数增长。

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

【讨论】:

    【解决方案4】:

    你可以像禁忌搜索一样进行:

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

    • 如果 i+1>=previous max then new max = i+1 reinialise tabu
    • 如果 i+1
    • 如果 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)

    【讨论】:

    • 这仍然是 O(MN),最坏的情况。
    • @Oli,不确定它是 NM,在最坏的情况下,循环的每 M 步最多有一个 M 个元素。所以 O(MN/M) = O(N) 并且每个循环一个测试所以总共是 O(2*N)。告诉我我是否错了
    • @Ricky Bobby:我认为你的程序不对。使用您的算法尝试递减列表,例如输入[13,12,11,10,9,8,7,6,5,4,3,2,1],结果错误。
    【解决方案5】:

    您可以使用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()]);
        }
    

    【讨论】:

    • 代码声明while (qi.Count &gt; 0 &amp;&amp; qi.PeekFront() &lt;= i - k)不正确。我认为应该是while (qi.Count &gt; 0 &amp;&amp; qi.Count &gt; k)
    • @vikaspachisia 你相信吗?你所说的是完全错误的。 -_- i-k 因为它在窗外。
    【解决方案6】:

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

    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;
     }
    

    我不明白这是递归还是线性做更好?

    【讨论】:

      猜你喜欢
      • 2019-10-10
      • 1970-01-01
      • 1970-01-01
      • 2019-06-04
      • 1970-01-01
      • 2023-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多