问题描述:有一个长度为n的数组,还有一个长度为k ≤ n的窗口。窗口一开始在最左边的位置,能看到元素{1, 2, 3, ..., k}。每次把窗口往右移动一个元素的位置,直到窗口的右边界到达数组的元素n。
案例:数组元素为{2, 7, 1, 6, -3, 2, 5, 0, 4},窗口长度k = 3。
输入包含n个元素的数组,以及窗口大小k,请依次给出各时刻中窗口中的最小值,用文字或伪代码描述你设计的算法并分析复杂度,算法复杂度越低越好。
最简单的方法:遍历 n-k+1次,每次让A[i]出队列,A[k+i-1]进队列,然后找出队列里最小值。这种方法的复杂度为: O(n-k)*O(k) ,不太好。
遍历n-k+1次是必须的,那么可以考虑减少findmin的次数。所以构造一个w_min队列:每次有数出窗口,如果它和w_min.front相等则队列pop;每次有数进入窗口,则比较w_min.back ,如果比队尾小则新数进队列;如果队列为空则在当前窗口进行一次复杂度为O(k)的findmin操作。这样就保证了队尾一定是窗口最小值。
算法实现:
#include <iostream>
#include <string.h>
#include <queue>
using namespace std;
int main () {
int n,k; cin>>n>>k;
int B[n];
k=k;
for (int i=0 ;i<n;) cin>>B[i++];
queue<int> w_min;
for (int i=0 ;i<=n-k;i++){
if(w_min.empty()) {
int temp=i;
for(int j=0;j<k;j++)
if(B[temp]>B[j+i])temp=j+i;
w_min.push(B[temp]);
}
else if(w_min.front()==B[i]) w_min.pop();
if(w_min.back()>B[k+i-1]) w_min.push(B[k+i-1]);
cout<<w_min.back()<<endl;
}
while(!w_min.empty()) w_min.pop();
system("pause");
return 0;
}
在循环中,除了 findmin,其他都是常数项的,而调用findmin的时机是队列为空,这种情况只有在窗口循环k次都没有新元素入队列才会发生,所以考虑最差的情况也是每k次循环调用1次。
所以,算法的时间复杂度为:
O(n-k)*O(1)+ O((n-k)/k)*O(k)=O(n-k)
O(n-k)*O(1)是除了循环中的常数项复杂度操作,O((n-k)/k)*O(k)是调用查询最小值操作次数与查询最小值复杂度乘积。