解析:(单调队列)
题目要求:
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。
大体思路:
使用暴力法的时间复杂度是O(mn) ,如何使得时间复杂度为O(n)?
使用单调队列,将滑动窗口中的值,放入单调队列中,使得队头元素最大,从队头到队尾一次单调递减。
这样每次去滑动窗口对应队列中的队首元素,即可作为当前滑动窗口的最大值。
面临问题:
问题①:如果对单调队列进行排序操作,时间复杂度还是O(mn),如何实现一个不需要排序的单调队列?
每次add()元素的时候:
如果队尾的元素比它小,直接破坏队尾的元素。新的队尾如果还比它小,继续破坏,直到队尾元素比它大或者队里所有元素都被破坏殆尽时,将这个元 素添加入队中。
如果队尾元素比它大或者等于它,直接将该元素添加到队尾。(两者相等的时候是不能干掉的与它相等的队尾的,只能直接将它添加到队尾,不然问题 ②的操作会不知道队头元素是不是需要删除的那个元素,还是只是后面某个和需要删除的相等的元素。相等时添加了,那么②中删队头元素的时候,就不 会出现多删误删)
经过这样的过程,可以一直保证,新来的元素足够大,就可以从队尾开始,逐个干掉队列中比它小的元素,直到遇到一个比它大元素,或者干掉了整 个队列位置。就能确保,从队尾到队头,元素依次递增。队头元素一定是最大的。
问题②:在①的操作中,是实现了滑动窗口每一次添加元素的方式,如何保实现滑动窗口每一次滑动时,需要删除窗口前的元素?
在①的操作中,其实滑动窗口是残缺的,因为很多元素被干掉了。那么滑动时需要删除的元素,也可能早在①的添加元素的操作中被干掉了。
如果需要删除的元素早就被干掉了,那么这里删除操作什么也不要做。
如果需要删除的元素没有被干掉,那么这里就要删除它。
所以,如何判断这个元素是否已经被删除?如果没有被删除,如何找到它?
滑动窗口要删除的元素,是这个窗口中最早进入队列的,队列先进先出,如果它没有被干掉,那一定就在队头。
只需要比较队头的元素和需要删除的元素是否相等,
相等,说明这个元素没有没有被干掉,删除队头元素即可。
不相等,说明这个元素早就被干掉了,不需要任何操作
实现步骤
步骤①:按照上述问题①和②的步骤,就可以自己定义一个MyQueue类,作为滑动窗口。
步骤②:初始化第一个滑动窗口,将nums中前k个元素按照步骤①的添加步骤,进行添加。并记录最大值,也就是队头元素。
步骤③:滑动窗口依次向后滑动,每一次执行步骤①中的删除操作,再执行添加操作,再记录最大值,也就是队头元素。
注意点:(LinkedList操作队尾的方法,getLast() ,removeLast())
虽然LinkedList的实现是基于双向链表,但是LinkedLsit类实现了Deque接口,可以把LinkedList当成Deque来用,所以Linkedlist就是队列
LinkedList操作队尾的方法:
getLast() 返回队尾元素,但是不删除对尾元素
removeLast() 返回队尾元素,并删除队尾元素
代码:
class MyQueue{
LinkedList<Integer> queue = new LinkedList<>();
public void MyQueue(){}
// 删除元素,删除就好,不需要返回值。
public void poll(int value) {
// 这是在移动滑动窗口时,如果队头的和要删除的元素相等,才需要删除的
// 队头元素和需要删除的元素不等,说明这里该删除的元素,在添加元素时它因为小于被添加的元素,早就被删除了。所以这里不需要任何操作。
if (!queue.isEmpty() && value == queue.peek()) {
queue.poll();
}
}
// 添加元素
public void add(int value) {
// 为了确保队头位置是当前窗口内最大的元素,所以必须保证队列里的元素时单调增加的
// 一旦添加的元素大于队尾元素,就破坏队尾元素,新的队尾元素如果还是小于添加的元素,继续破坏,直到遇到一个比它大的队尾元素,或者队列里的元素全被删除空了。
while (!queue.isEmpty() && value > queue.getLast()) {
queue.removeLast();
}
queue.add(value);
}
public int showMax() {
return queue.peek();
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] arr = new int[nums.length - k + 1];
MyQueue queue = new MyQueue();
// 先将前k个元素,添加到队列(
for (int i = 0; i < k; i++) {
queue.add(nums[i]);
}
// 添加第一个滑动窗口的最大值
arr[0] = queue.showMax();
// 上述操作相当于初始化了第一个滑动窗口
// 对后面的滑动窗口,从nums[k]开始,每滑动一次,实行出队和入队一次,再将最大值(队头元素)存入arr。
for (int i = k; i < nums.length; i++) {
queue.poll(nums[i - k]);
queue.add(nums[i]);
arr[i - k + 1] = queue.showMax();
}
return arr;
}
}