劍指Offer-66-滑動視窗的最大值
專案地址: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;
}
總結
第二種方法將最大值維持在指定位置的思路值得借鑑,同樣的還有單調棧問題。