项目地址:https://github.com/SpecialYy/Sword-Means-Offer
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
滑动窗口其实就是给定区间求出最大值而已,只不过这个窗口会不断向右滑动,现让你高效的求出每次滑动后当前窗口内的最大值。
老方法,暴力解决,这是不得已的情况下最坏的打算。就算是最低级代码,请你漂亮的且无bug的写出来。思路很简单,实现一个子函数用于遍历求出给定区间内最大值。主函数只要循环给出区间即可。
/**
* 普通做法,不断分段求最大值
* @param num
* @param size
* @return
*/
public ArrayList<Integer> maxInWindows(int [] num, int size) {
ArrayList<Integer> result = new ArrayList<>();
if (num == null || size <= 0 || size > num.length) {
return result;
}
for (int i = 0; i <= num.length - size; i++) {
result.add(maxNumOfInternal(num, i, i + size));
}
return result;
}
public int maxNumOfInternal(int[] num, int start, int end) {
int result = num[start];
for (int i = start + 1; i < end; i++) {
result = Math.max(result, num[i]);
}
return result;
}
经典的窗口求最值问题。思路为我们维护一个队头为当前窗口的最大值的双端队列。双端队列的两端均可增删节点。
首先是初始化窗口,因为初始化窗口的过程是扩展阶段,不需要弹出窗口左边的值。当队列为空时,直接进队列,此时队头就是最大值(就他自己)。接下来新加入的节点,若小于队头元素,说明它还是有可能成为窗口的最大值呢,前提是当队头的元素被剔除了,所以此时加入该节点。当新加入的节点,若大于当前队尾元素,说明该队尾永远都不可能成为窗口的最大值,所以剔除掉节点,继续比较新的队尾元素,直到队尾元素大于待加入节点。
当窗口初始化好之后,也就是窗口的右部分已扩展到指定位置。这时窗口就不要扩容了,只需不断向后滑动即可。滑动的过程当队列的大小等于窗口的大小要剔除头部的元素。加入新的节点策略依然要向初始化窗口阶段维护队头为最大值。该阶段记录窗口滑动过程中队头元素值即可,这就是题目要求的所有窗口的最大值集合。
这里有个问题,我们如何才能确保当前窗口确实要弹出头部节点呢?我们的策略可能会使窗口的大小不等于指定的size。这里解决的方案是队列存的不是值,而是索引,这样在加入节点,只需判断当前索引和队头最大值的索引是否等于size,若是则说明要弹出队头,否则按之前的策略判断加入节点即可。
/**
* 维护一个队头为最大值的队列,单调递减队列
* @param num
* @param size
* @return
*/
public ArrayList<Integer> maxInWindows2(int [] num, int size) {
ArrayList<Integer> result = new ArrayList<>();
if (num == null || size <= 0 || size > num.length) {
return result;
}
//初始化窗口
LinkedList<Integer> windows = new LinkedList<>();
for (int i = 0; i < size; i++) {
while (windows.size() != 0 && num[windows.getLast()] <= num[i]) {
windows.removeLast();
}
windows.addLast(i);
}
result.add(num[windows.getFirst()]);
for (int i = size; i < num.length; i++) {
if (i - windows.getFirst() == size) {
windows.removeFirst();
}
while(windows.size() != 0 && num[windows.getLast()] <= num[i]) {
windows.removeLast();
}
windows.addLast(i);
result.add(num[windows.getFirst()]);
}
return result;
}
第二种方法将最大值维持在指定位置的思路值得借鉴,同样的还有单调栈问题。