解决滑动窗口类的问题时,通用思路是使用双端队列的数据结构+存储元素下标
生成窗口最大值数组
题目:有一个整型数组arr和一个大小为w的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置,返回移动窗口的最大值数组
思路:本题的关键在于利用双端队列来实现窗口最大值的更新。首先生成双端队列qmax,qmax中存放数组arr中的下标。
遍历规则:遍历到arr[i],如果qmax为空,直接放入,如果不为空,比较与队尾元素的大小,如果大于则将队尾元素出队列直到遇见一个小于的,则入队列
原理:如果遍历到的元素大于在它前面的元素,那么前面元素将不会对我们所需要的最大值数组的结果产生任何影响,因此可以放心的将其移出队列
private static int[] getWindowMax(int[] arr, int w) {
// 需要转换一下思维,队列不存储数组元素,而存储下标
// 每次入之前比较与队尾元素的大小,小就入,大就取代
// 这里可以取代的原因是下标是顺次变大的,
// 如果下标和元素大小都大的话,那么都小的元素对最终结果就不会产生任何影响
LinkedList<Integer> maxQueue = new LinkedList<>();
// 这里还需要额外注意一下,如果队头元素下标到达距离当前下标差距达到指定值时,也要出队列
int[] res = new int[arr.length - w + 1];
int index = 0;
for(int i = 0;i < arr.length;i++) {
while(!maxQueue.isEmpty() && arr[i] >= arr[maxQueue.peekLast()]) {
maxQueue.pollLast();
}
maxQueue.add(i);
if(maxQueue.peekFirst() == i - w) {
maxQueue.pollFirst();
}
if(i >= w - 1) {
res[index++] = arr[maxQueue.peekFirst()];
}
}
return res;
}
最大值减去最小值小于或等于num的子数组
题目:给定数组arr和整数num,共返回有多少个子数组满足数组内最大最小值的差值小于等于num。
要求:实现时间复杂度为O(N)的解法
思路:本质上还是双端队列的思路,同时需要一个前置的理论基础
双端队列:生成两个双端队列qmax和qmin,分别维护子数组的最大最小值
指针移动的理论基础:
- 如果子数组i-j满足条件,那么子树组的子数组也满足条件
- 如果子树组i-j不满足条件,那么所有包含该子树组的数组都不满足条件
遍历规则:每一次循环保持i不懂,j向右直到不满足条件,那么就求出了以i为左端点的所有子数组,然后i++,j可以继续向右而不必从0开始,因为根据理论基础1,所有的i+1-j都是满足条件的,因此最后i,j各遍历一次数组,时间复杂度O(N)
private static int getNum(int[] arr, int num) {
// 根本原理是双端队列+窗口包含性
// 如果i-j符合标准,那么所有子集都符合标准;如果i-j不符合标准,那么所有父集都不符合标准
if (arr.length == 0) {
return 0;
}
LinkedList<Integer> qmin = new LinkedList<>();
LinkedList<Integer> qmax = new LinkedList<>();
int i = 0;
int j = 0;
int res = 0;
while (i < arr.length) {
while (j < arr.length) {
while (!qmin.isEmpty() && arr[qmin.peekLast()] >= arr[j]) {
qmin.pollLast();
}
qmin.addLast(j);
while (!qmax.isEmpty() && arr[qmin.peekLast()] <= arr[j]) {
qmax.pollLast();
}
qmax.addLast(j);
if (arr[qmax.getFirst()] - arr[qmin.getFirst()] > num) {
break;
}
j++;
}
if (qmin.peekFirst() == i) {
qmin.pollFirst();
}
if (qmax.peekFirst() == i) {
qmax.pollFirst();
}
res += j - i;
i++;
}
return res;
}