Algorithm 长度为K的滑动窗口的最大元素之和

Algorithm 长度为K的滑动窗口的最大元素之和,algorithm,Algorithm,最近我遇到了一个问题。算法部分要求计算长度K的滑动窗口的最大元素之和。其中K的范围为1给定窗口大小的滑动窗口中的最大值之和可以使用保留当前窗口中元素的双端队列在线性时间内计算。我们维护deque,以便队列中的第一个(索引0,最左边)元素始终是当前窗口的最大值 这是通过对数组进行迭代来完成的,在每次迭代中,首先,如果deque中的第一个元素不再在当前窗口中,我们将其删除(我们通过检查其原始位置来完成此操作,该位置也与它的值一起保存在deque中)。然后,我们从deque的末尾删除任何小于当前元素的

最近我遇到了一个问题。算法部分要求计算长度K的滑动窗口的最大元素之和。其中K的范围为1给定窗口大小的滑动窗口中的最大值之和可以使用保留当前窗口中元素的双端队列在线性时间内计算。我们维护deque,以便队列中的第一个(索引0,最左边)元素始终是当前窗口的最大值

这是通过对数组进行迭代来完成的,在每次迭代中,首先,如果deque中的第一个元素不再在当前窗口中,我们将其删除(我们通过检查其原始位置来完成此操作,该位置也与它的值一起保存在deque中)。然后,我们从deque的末尾删除任何小于当前元素的元素,最后将当前元素添加到deque的末尾

计算大小为K的所有滑动窗口的最大值的复杂度为O(N)。如果要对大小为1..N的K的所有值进行计算,则时间复杂度为O(N^2)。O(N)是计算大小为K的所有窗口的最大值之和的最佳时间(这很容易看到)。要计算K的其他值之和,简单的方法是对K的每个不同值重复计算,这将导致总时间为O(N^2)。有更好的办法吗?不,因为即使我们将计算结果保存为一个K值,我们也无法使用它在不到O(N)的时间内计算不同K值的结果。所以最佳时间是O(N^2)

以下是python中的一个实现:

from collections import deque

def slide_win(l, k):
  dq=deque()
  for i in range(len(l)):
    if len(dq)>0 and dq[0][1]<=i-k:
      dq.popleft()
    while len(dq)>0 and l[i]>=dq[-1][0]:
      dq.pop()
    dq.append((l[i],i))
    if i>=k-1:
      yield dq[0][0]

def main():
  l=[5,3,12,4]
  print("l="+str(l))
  for k in range(1, len(l)+1):
    s=0
    for x in slide_win(l,k):
      s+=x
    print("k="+str(k)+" Sum="+str(s))
从集合导入数据
def幻灯片_win(左、右):
dq=deque()
对于范围内的i(len(l)):
如果len(dq)>0且dq[0][1]0和l[i]>=dq[-1][0]:
dq.pop()
dq.追加((l[i],i))
如果i>=k-1:
收益率dq[0][0]
def main():
l=[5,3,12,4]
打印(“l=“+str(l))
对于范围(1,len(l)+1)内的k:
s=0
对于幻灯片中的x_win(l,k):
s+=x
打印(“k=“+str(k)+”Sum=“+str(s))

让我们为每个元素确定一个区间,其中该元素占主导地位(最大值)。我们可以在线性时间内使用堆栈向前和向后运行。数组L和R将包含超出控制区间的索引

要获取左右索引,请执行以下操作:

Stack.Push(0) //(1st element index)
for i = 1 to Len - 1 do
     while Stack.Peek < X[i] do
         j = Stack.Pop
         R[j] = i   //j-th position is dominated by i-th one from the right
     Stack.Push(i)
while not Stack.Empty
   R[Stack.Pop] = Len  //the rest of elements are not dominated from the right

//now right to left
Stack.Push(Len - 1) //(last element index)
for i = Len - 2 to 0 do
     while Stack.Peek < X[i] do
         j = Stack.Pop
         L[j] = i   //j-th position is dominated by i-th one from the left
     Stack.Push(i)
while not Stack.Empty
   L[Stack.Pop] = -1  //the rest of elements are not dominated from the left
现在,对于每一个元素,我们可以在每一个可能的总数中计算它的影响。
元素5在(0,0)区间占主导地位,它只在k=1和条目中求和
元素7在(0,2)区间占主导地位,它在k=1和条目中求和一次,在k=2条目中求和两次,在k=3条目中求和一次。
元素3在(2,2)区间占主导地位,它只在k=1和条目中求和
元素9在(0,4)区间占主导地位,在k=1和条目中求和一次,在k=2中求和两次,在k=3中求和两次,在k=4中求和两次,在k=5中求和一次。
元素4在(4,4)区间占主导地位,它只在k=1和项中求和

一般情况下,长数组中心具有长支配间隔的元素可能会放弃k长度和中的k*值影响(这取决于相对于数组末端和另一个dom.elements的位置)

请注意,系数之和为N*(N-1)/2(等于可能的窗口数),大多数表项为空,因此复杂性似乎优于O(N^2)

(我仍然怀疑确切的复杂性)

这里是O(n)的简略示意图

对于每个元素,确定左侧的连续元素不大于多少(称为
a
),右侧的连续元素小于多少(称为
b
)。这可以在时间O(n)内为所有元素完成——参见MBo的答案

如果某个特定元素在其窗口中包含该元素,并且仅在其左侧的to
A
和右侧的
b
之间包含该元素,则该元素在其窗口中最大。有用的是,这种长度为k的窗口的数量(以及这些窗口的总贡献)在k中是分段线性的,最多有五个。例如,如果
a=5
b=3
,则

1 window  of size 1
2 windows of size 2
3 windows of size 3
4 windows of size 4
4 windows of size 5
4 windows of size 6
3 windows of size 7
2 windows of size 8
1 window  of size 9.
我们需要的数据结构是一个Fenwick树,它的值不是数字而是k的线性函数。对于分段线性贡献函数的每个线性部分,我们将其添加到其间隔开始处的单元中,并从结束处的单元中减去它(闭合的开始,开放的结束)。最后,我们检索所有前缀和,并在其索引k处对它们求值以得到最终数组

(好的,现在必须运行,但是我们实际上不需要第二步的Fenwick树,这将复杂性降低到O(n),并且可能还有一种方法可以在线性时间内执行第一步。)

Python 3,轻度测试:

def left_extents(lst):
  result = []
  stack = [-1]
  for i in range(len(lst)):
    while stack[-1] >= 0 and lst[i] >= lst[stack[-1]]:
      del stack[-1]
    result.append(stack[-1] + 1)
    stack.append(i)
  return result


def right_extents(lst):
  result = []
  stack = [len(lst)]
  for i in range(len(lst) - 1, -1, -1):
    while stack[-1] < len(lst) and lst[i] > lst[stack[-1]]:
      del stack[-1]
    result.append(stack[-1])
    stack.append(i)
  result.reverse()
  return result


def sliding_window_totals(lst):
  delta_constant = [0] * (len(lst) + 2)
  delta_linear = [0] * (len(lst) + 2)
  for l, i, r in zip(left_extents(lst), range(len(lst)), right_extents(lst)):
    a = i - l
    b = r - (i + 1)
    if a > b:
      a, b = b, a
    delta_linear[1] += lst[i]
    delta_linear[a + 1] -= lst[i]
    delta_constant[a + 1] += lst[i] * (a + 1)
    delta_constant[b + 2] += lst[i] * (b + 1)
    delta_linear[b + 2] -= lst[i]
    delta_linear[a + b + 2] += lst[i]
    delta_constant[a + b + 2] -= lst[i] * (a + 1)
    delta_constant[a + b + 2] -= lst[i] * (b + 1)
  result = []
  constant = 0
  linear = 0
  for j in range(1, len(lst) + 1):
    constant += delta_constant[j]
    linear += delta_linear[j]
    result.append(constant + linear * j)
  return result

print(sliding_window_totals([5, 3, 12, 4]))
def left_区段(lst):
结果=[]
堆栈=[-1]
对于范围内的i(len(lst)):
当堆栈[-1]>=0和lst[i]>=lst[stack[-1]]时:
del堆栈[-1]
结果.追加(堆栈[-1]+1)
stack.append(i)
返回结果
def右扩展数据块(lst):
结果=[]
堆栈=[len(lst)]
对于范围内的i(len(lst)-1,-1,-1):
而堆栈[-1]lst[stack[-1]]:
del堆栈[-1]
result.append(堆栈[-1])
stack.append(i)
result.reverse()
返回结果
def滑动窗口总计(lst):
delta_常数=[0]*(len(lst)+2)
delta_线性=[0]*(len(lst)+2)
对于zip中的l、i、r(左范围(lst)、范围(长范围(lst))、右范围(lst)):
a=i-l
b=r-(i+1)
如果a>b:
a、 b=b,a
delta_线性[1]+=lst[i]
delta_线性[a+1]=lst[i]
δu常数[a+1]+=lst[i]*(a+1)
δu常数[b+2]+=lst[i]*(b+1)
delta_线性[b+2]=lst[i]
delta_线性[a+b+2]+=lst[i]
δu常数[a+b+2]=lst[i]*(a+1)
δu常数[a+b+2]=lst[i]*(b+1)
结果=[]
常数=0
线性=0
对于范围(1,len(lst)+1)内的j:
常数+=δ_常数[j]
线性+=delta_线性
1 window  of size 1
2 windows of size 2
3 windows of size 3
4 windows of size 4
4 windows of size 5
4 windows of size 6
3 windows of size 7
2 windows of size 8
1 window  of size 9.
def left_extents(lst):
  result = []
  stack = [-1]
  for i in range(len(lst)):
    while stack[-1] >= 0 and lst[i] >= lst[stack[-1]]:
      del stack[-1]
    result.append(stack[-1] + 1)
    stack.append(i)
  return result


def right_extents(lst):
  result = []
  stack = [len(lst)]
  for i in range(len(lst) - 1, -1, -1):
    while stack[-1] < len(lst) and lst[i] > lst[stack[-1]]:
      del stack[-1]
    result.append(stack[-1])
    stack.append(i)
  result.reverse()
  return result


def sliding_window_totals(lst):
  delta_constant = [0] * (len(lst) + 2)
  delta_linear = [0] * (len(lst) + 2)
  for l, i, r in zip(left_extents(lst), range(len(lst)), right_extents(lst)):
    a = i - l
    b = r - (i + 1)
    if a > b:
      a, b = b, a
    delta_linear[1] += lst[i]
    delta_linear[a + 1] -= lst[i]
    delta_constant[a + 1] += lst[i] * (a + 1)
    delta_constant[b + 2] += lst[i] * (b + 1)
    delta_linear[b + 2] -= lst[i]
    delta_linear[a + b + 2] += lst[i]
    delta_constant[a + b + 2] -= lst[i] * (a + 1)
    delta_constant[a + b + 2] -= lst[i] * (b + 1)
  result = []
  constant = 0
  linear = 0
  for j in range(1, len(lst) + 1):
    constant += delta_constant[j]
    linear += delta_linear[j]
    result.append(constant + linear * j)
  return result

print(sliding_window_totals([5, 3, 12, 4]))