一、滑动窗口就是一个双端队列。
窗口内最大值的求解
双端队列,代价都是O(1),不需要这个结构表示第几个数是最大值,只需要双端队列可以依次增加或减少数值。
双端队列中每次保存两个值,当前的数值和当前的位置信息。
加数
- 每次小的值从右边进入双端队列,整个队列形成从大到小排列的顺序。
- 如果出现新的数值大于最右边的小数值,则将小的数值弹出,直到放得下新的数值。
- 如果出现相等的数值,则将先前的数值弹出,将新的数值保存
减数
L移动的时候,分析当前最大值所在的位置信息是否过期,如果过期则弹出,没过期则显示当前的最大值。
Java代码
public static int[] getMaxWindow(int[] arr,int w){
if(arr == null || w<1 || arr.length <w){
return null;
}
LinkedList<Integer> qmax = new LinkedList<Integer>();
int[] res = new int[arr.length-w+1];
int index = 0;
for(int i = 0;i<arr.length;i++){
//双端队列不为空,并且尾部的数值小于新加入的数值,则弹出尾部下标
while(!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[i]){
qmax.pollLast();
}
qmax.addLast(i);
//判断首位上的数据是否过期,进行下标和i-w的判断,过期就弹出下标,不过期就继续下面的操作
//窗口如果没有形成完全,则不会弹出。此处针对不同题目需要进行修改,看窗口是否要全完全显示之后才开始计算最大值
if(qmax.peekFirst() == i-w){
qmax.pollFirst();
}
//如果i还在窗口之中,则返回第一个数值。
if(i>=w-1){
res[index++] = arr[qmax.peekFirst()];
}
}
return res;
}
二、单调栈
原始题目:在一个数组中想知道所有数中,左边离他近的比他大的和右边离他近的比他大的数
有一个数组[54361207],
拿一个栈,依次压入。5,然后压入4,4比5小,可以进。然后再压3,3比4小可以进。然后压6,6比3小,那么6就是3的右边的大数。而3底下压着的那个数据就是左边的最接近它的比它大的。然后把3弹出。继续看6压入栈的情况。此时就是来到了4,6比4大,同理可得。如果遍历完了数组,栈中还有元素。那么单独清算这些元素。这些元素都没有右边比它大的值,也就是无,而左边比它大的值就是栈中它底下的那个。栈底的就是左右都没有比它大的数。如果有重复值的时候,压栈的时候如果有重复值就把重复值上面的元素用链表表示起来,在栈中属于同一个位置。当压栈下一个比当前数要大的时候,弹出这个链表中的最后一个位置。一样的。就是遇见相同值的时候就存起来。
单调栈的应用
定义:数组中累计和与最小值的乘积,假设叫做指标A。
给定一个数组,请返回子数组中,指标A最大的值。
思路:大流程:遍历每一个元素,以当前元素为子数组中的最小值,然后去扩充数组,得到A指标。遍历完了,取出最大的那个就是了。
例如数组[5,3,2,1,6,7,8,5]
第一个为5的时候。因为要满足当前数为最小值,所以这个子数组不能往右边扩了,只能是5.A指标就是5x5=25.然后来到第二个,也就是3,要满足当前3为子数组的最小值,也就是不能往右扩,因为2比3要小,不满足3是最小值,然后看3的左边,是5,比3大,可以扩充,因为子数组[3,5]比[3]的A指标要大。然后来到2,一样的是[5,3,2],然后来到了1,1作为最小值进行扩充子数组,发现左右两边都没有比它小的,那么A指标肯定是元素越多越大,那么就是全数组了。其实最终也就是用单调栈(找当前数左右两边离得最近的小于它的值)来解决,来到当前位置i,只进栈和出栈一次就可以确定一个元素的指标A,有n个数,所以时间复杂度为O(N)。