核心思想:优先队列
思路:
建立一个大根堆的优先队列,队头永远是当前最大元素。为了方便记录每个元素在原数组中的位置,我们把优先队列中的元素设置为int数组,int[0]存储数值,int[1]存储位置索引。
首先,把前k个元素的int数组存入队列中;然后创建ans数组,存储每个滑动窗口中最大元素数值,其长度为n-k+1,首先另ans[0]=队头元素;接着,遍历剩余的n-k个元素,每次都会将新的元素加入到优先队列中,同时判断当前优先队列中,有没有元素的位置比j-k小的,小的话就弹出(为什么?因为[0,j-k]是滑动窗口左边的元素集合,而j-k是边界,凡是比这个小的,都是窗口之外的元素)(为什么用while,因为不是每次循环必定弹出队头,所以不用设置成长度为k的优先队列,先存着,如果出现队头在滑动窗口之外,就弹出直至到达滑动窗口内);同时更新每个窗口的最大值在ans[j]中;最后,但会ans数组。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>(){
public int compare(int[] pair1, int[] pair2){
return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
}
});
for(int i = 0; i < k; i++){
queue.offer(new int[]{nums[i], i});
}
int[] ans = new int[nums.length - k + 1];
ans[0] = queue.peek()[0];
for(int j = k; j < nums.length; j++){
queue.offer(new int[]{nums[j], j});
while(queue.peek()[1] <= j - k){
queue.poll();
}
ans[j - k + 1] = queue.peek()[0];
}
return ans;
}
}
下边是最优解,速度最快的方法:
核心思想:单调栈
思路:
维护一个单调递减的栈,当滑动窗口向右移动时,我们需要把一个新的元素放入队列中。为了保持队列的性质(先进先出),我们会不断地将新的元素与队尾的元素相比较,如果前者大于等于后者,那么队尾的元素就可以被永久地移除,我们将其弹出队列。我们需要不断地进行此项操作,直到队列为空或者新的元素小于队尾的元素。
由于队列中下标对应的元素是严格单调递减的,因此此时队首下标对应的元素就是滑动窗口中的最大值。但与上述方法相同的是,此时的最大值可能在滑动窗口左边界的左侧,并且随着窗口向右移动,它永远不可能出现在滑动窗口中了。因此我们还需要从队首弹出元素,直到队首元素在窗口中为止。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> deque = new LinkedList<>();
for(int i = 0; i < k; i++){
while(!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]){
deque.pollLast();
}
deque.offerLast(i);
}
int[] ans = new int[nums.length - k + 1];
ans[0] = nums[deque.peekFirst()];
for(int j = k; j < nums.length; j++){
while(!deque.isEmpty() && nums[j] > nums[deque.peekLast()]){
deque.pollLast();
}
deque.offerLast(j);
//添加一个元素,可能队列长度长度超出k
if(deque.peekFirst() <= j - k){
deque.pollFirst();
}
ans[j - k + 1] = nums[deque.peekFirst()];
}
return ans;
}
}
这个方法与上面把那个方法最大的不同就是:下边的方法可以保证队头元素一定是最远元素;而且下边的方法队列最长为k+1.