题目描述
思路
暴力求解
首先这个问题只是划定一个数组的范围,叫做窗口,然后输出这个窗口的最大值,直接暴力求解的思路比较简单。
即将每次移动后的窗口找出最大值然后输出即可
窗口的大小为k
数组长度为n
那么数组移动次数为n-k
那么时间复杂度为O((n-k+1)k),即O(nk)
会超出时间限制
优先队列优化
对于每次窗口的移动,改变的只有一个元素(或者两个,一进一出),可以利用这个特点进行优化
因为每次找的都是窗口的最大值,那么优先队列(堆实现)就是最合适的数据结构了
优先队列中存入二元组(元素值,元素索引)
结合优先队列的特点,这个算法的时间复杂度为O(nlgn)
代码如下
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
//优先队列中的元素是一个二元组
//(元素值,元素的索引)
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>(){
public int compare(int[] pair1,int[] pair2){
//return pair2[0]-pair1[0];
//判断元素值是否相同
//如果不同,返回差值(这里是让pair[2] - pair[1],意为构建一个最大优先队列)
//如果相同,返回元素索引的差值,即在元素值相同的情况下,将数组后面的元素放在前面
//这样做的目的是为了减少后面的移除工作
return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
}
});
//添加前k个元素到优先队列中
for(int i=0;i<k;++i){
//添加的元素:(元素值,元素索引)
pq.offer(new int[]{nums[i],i}); //offer() : 添加一个元素到队列中
}
int[] ans = new int[n-k+1];
ans[0] = pq.peek()[0]; //peek() : 返回队头元素,但并不移除队头元素
for(int i=k;i<n;++i){//向前移动窗口,从数组的第k+1个元素开始
pq.offer(new int[]{nums[i],i});
while(pq.peek()[1] <= i-k){//如果队首元素已经出了窗口,就把这些元素永久的移除
//注意这里只在队首元素做了移除工作,而对于队中其他超出窗口的元素并没有做这样的工作,
//因为其他元素在队中并不影响结果的输出
pq.poll();//poll(),返回并移除队首元素
}
ans[i-k+1] = pq.peek()[0];
}
return ans;
}
}
记录一下我的一个逻辑漏洞:
本来是想着当将新的元素入队之前,检查一下现在队首元素是不是还是在窗口内,如果不在窗口内就插入这个元素;如果在窗口内就没必要插入这个新元素了(省去了lgn的时间)。
但实际上这是错误的,因为无法保证这个新元素在后面的过程中不会成为队首元素
也就是说现在插入的元素也许在这一轮不是队首元素,但是有可能随着此时的队首元素被舍弃,它在后面某轮中成为队首元素