1. 程式人生 > 實用技巧 >劍指Offer_#59-I_滑動視窗的最大值

劍指Offer_#59-I_滑動視窗的最大值

劍指Offer_#59-I_滑動視窗的最大值

劍指offer

Contents

題目

給定一個數組 nums 和滑動視窗的大小 k,請找出所有滑動窗口裡的最大值。
示例:

輸入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
輸出: [3,3,5,5,6,7] 
解釋: 

  滑動視窗的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

提示:
你可以假設 k 總是有效的,在輸入陣列不為空的情況下,1 ≤ k ≤輸入陣列的大小。

思路分析

用一個雙端佇列維護視窗的最大值,保證雙端佇列是非嚴格遞減的(前一個元素大於等於後一個元素),那麼雙端佇列的第一個元素正好就是當前視窗當中的最大值。
演算法流程
遍歷陣列,維護兩個指標i,j,分別指向視窗左右邊界,右指標從第一個元素開始,左指標則始終與右指標相距k-1(中間剛好有k個元素),指標每次移動後執行如下過程:

  1. i>0時(在此之前視窗還未形成),如果當前佇列的頭部恰好是nums[i-1],則將其從佇列裡刪除
  2. 比較nums[j]與隊列當中的數字(從後往前看),將小於nums[j]的全部刪除(因為nums[j]是當前新加入的數字,比它小的絕不可能是視窗最大值)
  3. 將nums[j]加入到佇列尾部,這時整個佇列是非嚴格遞減的,頭部的值就是當前視窗的最大值
  4. 如果i >= 0,將頭部數字加入到res中

總結起來,就是維護一個特殊的雙端佇列資料結構,視窗滑動時,將上個視窗的第一個元素刪除,再把上個視窗之後的第一個元素加入進來,且保證佇列頭部數字最大。

細節問題:

  1. 指標初始值:j = 0,i = 0-(k - 1) = 1-k
  2. 結果陣列res的長度:從第k個元素開始,到第n個元素,有幾個數字?n-k+1
  3. java的雙端佇列:Deque<Integer> deque = new LinkedList<>();
    • peekFirst(),peekLast()
    • removeFirst(),removeLast()
    • addLast
  4. 特殊情況:空陣列,k = 0,返回空陣列
  5. 刪除元素的時候,必須保證佇列非空

解答

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        if(n == 0 || k == 0) return new int[0];
        Deque<Integer> deque = new LinkedList<>();
        //res的長度就是滑動視窗的個數,共有n-k+1個
        int[] res = new int[n - k + 1];
        //ERROR:不可以寫成 int i = 1 - k,int j = 0; 因為這樣寫是兩個statement
        for(int i = 1 - k,j = 0; j <= n - 1; i++, j++){
            //1.如果當前視窗刪除掉的nums[i - 1]恰好時上一個視窗的最大值,在佇列中將這個元素刪除
            if(i >= 1 && deque.peekFirst() == nums[i - 1])
                deque.removeFirst();
            //2.當前視窗新加入nums[j],小於此值的絕對不會是最大值,所以直接從佇列將這些較小的數字刪除(從後往前)
            while(!deque.isEmpty() && nums[j] > deque.peekLast())
                deque.removeLast();
            //3.將新加入的nums[j]加入佇列末尾
            deque.addLast(nums[j]);
            //4.當前視窗最大值就是佇列頭部,將其寫入res
            if(i >= 0) res[i] = deque.peekFirst();
        }
        return res;
    }
}

複雜度分析

時間複雜度:O(n),遍歷整個陣列
空間複雜度:O(k),因為雙端佇列裡邊的數字始終就是當前視窗的數字,所以佇列的大小是k