Sliding window maximum in O(n) time

落爺英雄遲暮 提交于 2020-07-14 07:06:50

问题


Input:

listi = [9, 7, 8, 4, 6, 1, 3, 2, 5]

Output:

# m=3
listo = [9, 8, 8, 6, 6, 3, 5]

Given a random list composed of n numbers, I need to find all of the sublists of m consequtive elements, choose the largest value from the sublist and put them in a new list.

def convert(listi, m):
    listo = []
    n = len(listi)
    for i in range(n-m+1):
        listo.append(max(listi[i:3+i]))
    return listo

The time complexity for this implementation is O(m\^{(n-m+1)}, which is pretty bad if listi is long, is there a way to implement this in the complexity of O(n)?


回答1:


Surprisingly, the easily accessible descriptions of this algorithm are not that easy to understand, so the trick is this:

As you slide a window of length m over your list of length n, you maintain a deque of all the elements in the current window that might, at some point, become the maximum in any window.

An element in the current window might become the maximum if it is greater than all the elements that occur after it in the window. Note that this always includes the last element in the current window.

Since every element in the deque is > all the elements after it, elements in the deque are monotonically decreasing, and the first one is therefore the maximum element in the current window.

As the window slides one position to the right, you can maintain this deque as follows: remove all the elements from the end that are <= the new element. Then, add the new element to the end of the deque. If the element that drops off the front of the window is the first element in the deque, then remove it. Since each element is added and removed at most one time, the total time required to maintain this deque is in O(n).

To make it easy to tell when an element at the front of the deque is falls out of the window, store the elements' indexes in the deque instead of their values.

Here's a reasonably efficient python implementation:

def windowMax(listi, m):
    # the part of this list at positions >= qs is a deque
    # with elements monotonically decreasing.  Each one
    # may be the max in a window at some point
    q = []
    qs = 0

    listo=[]
    for i in range(len(listi)):

        # remove items from the end of the q that are <= the new one
        while len(q) > qs and listi[q[-1]] <= listi[i]:
            del q[-1]

        # add new item
        q.append(i)

        if i >= m-1:
            listo.append(listi[q[qs]])
            # element falls off start of window
            if i-q[qs] >= m-1:
                qs+=1

        # don't waste storage in q. This doesn't change the deque
        if qs > m:
            del q[0:m]
            qs -= m
    return listo



回答2:


There is a beautiful solution with a running time independent of M.

In the figure below, the first row represents the initial sequence.In the second row, we have the maxima of groups of 1, 2, … M consecutive elements from left to right ("prefix" maxima). In the third row, we have the maxima of groups of 1, 2, … M consecutive elements, from right to left ("suffix" maxima). And in the fourth row, the maxima of elements of the second and third rows.

a   b   c    d    e    f    g    h    i    j    k    l    m    n    o

a   ab  abc  d    de   def  g    gh   ghi  j    jk   jkl  m    mn   mno
        abc  bc   c    def  ef   f    ghi  hi   i    jkl  kl   l    mno  no   o

        abc  bcd  cde  def  efg  fgh  ghi  hij  ijk  jkl  klm  lmn  mno          

Note that there are replicated elements in row three, which we needn't compute.

The computation of the second row takes M-1 comparisons per slice of M elements; the second row M-2, and the third M. So ignoring the effect at the ends, we perform slightly less than 3 comparisons per element.

The required storage is an additional array of M elements to temporarily evaluate slices of the third row.




回答3:


I tried timing with zip and it seems the result is 50% faster than your current function - can't really tell the time complexity difference though.

import timeit

setup = """
from random import randint
listi = [randint(1,100) for _ in range(1000)]

def convert(iterable, m):
    t = [iterable[x:] for x in range(m)]
    result = [max(combo) for combo in zip(*t)]
    return result"""

print (min(timeit.Timer('a=listi; convert(a,3)', setup=setup).repeat(7, 1000)))
#0.250054761


setup2 = """
from random import randint
listi = [randint(1,100) for _ in range(1000)]

def convert2(listi, m):
    listo = []
    n = len(listi)
    for i in range(n-m+1):
        listo.append(max(listi[i:3+i]))
    return listo"""

print (min(timeit.Timer('a=listi; convert2(a,3)', setup=setup2).repeat(7, 1000)))
#0.400374625


来源:https://stackoverflow.com/questions/57212532/sliding-window-maximum-in-on-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!