簡體   English   中英

O(n) 時間內的滑動窗口最大值

[英]Sliding window maximum in O(n) time

輸入:

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

輸出:

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

給定一個由n數字組成的隨機列表,我需要找到m連續元素的所有子列表,從子列表中選擇最大值並將它們放入一個新列表中。

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

這個實現的時間復雜度是O(m\\^{(n-m+1)} ,如果listi很長,這很糟糕,有沒有辦法在O(n)的復雜度中實現它?

令人驚訝的是,這個算法的易於訪問的描述並不那么容易理解,所以訣竅是這樣的:

當您在長度為n的列表上滑動長度為m的窗口時,您會維護當前窗口中所有元素的雙端隊列,該雙端隊列可能在某些時候成為任何窗口中的最大值。

如果當前窗口中的元素大於窗口中出現在它之后的所有元素,則它可能會成為最大值。 請注意,這始終包括當前窗口中的最后一個元素。

由於雙端隊列中的每個元素都 > 其后的所有元素,因此雙端隊列中的元素是單調遞減的,因此第一個是當前窗口中的最大元素。

當窗口向右滑動一個位置時,您可以按如下方式維護此雙端隊列:從末尾刪除 <= 新元素的所有元素。 然后,將新元素添加到雙端隊列的末尾。 如果從窗口前端掉落的元素是雙端隊列中的第一個元素,則將其刪除。 由於每個元素最多添加和刪除一次,因此維護此雙端隊列所需的總時間為 O(n)。

為了便於判斷位於雙端隊列前面的元素何時掉出窗口,請將元素的索引而不是它們的值存儲在雙端隊列中。

這是一個相當有效的python實現:

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

有一個漂亮的解決方案,它的運行時間與 M 無關。

在下圖中,第一行表示初始序列。在第二行中,我們有從左到右的 1, 2, ... M 個連續元素組的最大值(“前綴”最大值)。 在第三行,我們有 1, 2, ... M 個連續元素組的最大值,從右到左(“后綴”最大值)。 在第四行,第二行和第三行元素的最大值。

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          

請注意,第三行中有重復的元素,我們不需要計算它們。

第二行的計算對每片 M 個元素進行 M-1 次比較; 第二行 M-2 和第三行 M。因此忽略末尾的影響,我們對每個元素執行略少於 3 次的比較。

所需的存儲是一個額外的 M 個元素數組,用於臨時評估第三行的切片。

我嘗試使用zip計時,結果似乎比您當前的功能快 50% - 雖然無法真正說出時間復雜度的差異。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM