1. 程式人生 > >劍指Offer-66-滑動視窗的最大值

劍指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;
    }

總結

第二種方法將最大值維持在指定位置的思路值得借鑑,同樣的還有單調棧問題。