[英]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.