我们给出了一个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
我通过跟踪每个点的窗口的最大元素来解决问题,但是当最大的元素从窗口滑出时遇到了问题。那时,我想不出一个快速的方法来弄清楚剩下的最大元素是什么。
有谁知道解决这个问题的有效算法?
答案 0 :(得分:11)
This older question 讨论了如何在O(1)时间内构建支持insert,dequeue和find-min的队列数据结构。请注意,这是不标准优先级队列,而是一个队列,在任何时候您都可以在O(1)时间内找到它包含的最小元素的值。您可以轻松修改此结构以支持O(1)中的find-max而不是find-min,因为这与此特定问题更相关。
使用此结构,您可以在O(n)时间内解决此问题,如下所示:
这需要总共O(n)次,因为您访问每个数组元素一次,每次最多入队和出列一次,并且将find-max调用恰好n-k次。我认为这很酷,因为复杂性独立于k ,最初似乎不一定是可能的。
希望这有帮助!谢谢你提出一个很酷的问题!
答案 1 :(得分:1)
您可以保留当前元素的二进制搜索树,例如,将它们保存为值出现对。除此之外,你的滑动窗口算法应该足够好了。
这样,选择最大值(子节中的最大元素)将花费O(logL)时间,L是当前子节的长度;添加新也将是O(logL)。要删除最旧的一个,只需搜索该值并将计数减1,如果计数变为0则删除它。
因此总时间为O(NlogL),N为数组的长度。
答案 2 :(得分:1)
您可以像禁忌搜索一样继续:
遍历列表并获得第4个第i个元素的最大值。 然后在下一步中检查第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)
答案 3 :(得分:1)
我能想到的最好的是O(n log m)。 你可以通过动态编程来实现它。
在第一遍中,您会发现每个元素的最大值是元素本身和下一个元素的最大值。
现在你有n个最大值(窗口大小= 2)。
现在你可以在这个数组中找到每个元素的最大值和这个数组中的overnext(给出每个元素下一个4的最大值,即窗口大小= 4)。
然后你可以再次这样做(并且每次窗口大小翻倍)。
正如人们清楚地看到窗口大小呈指数级增长。
因此运行时为O(n log m)。实现有点棘手,因为你必须考虑角落和特殊情况(特别是当窗口大小不应该是2的幂),但它们不会影响渐近运行时。
答案 4 :(得分:1)
使用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()]);
}
答案 5 :(得分:0)
请查看我的代码。据我所知,我认为这个算法的时间复杂度是
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;
}
我不明白在递归时是做得更好还是线性做?