劍指Offer_#59-I_滑動視窗的最大值
阿新 • • 發佈:2020-07-27
劍指Offer_#59-I_滑動視窗的最大值
劍指offerContents
題目
給定一個數組 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個元素),指標每次移動後執行如下過程:
- i>0時(在此之前視窗還未形成),如果當前佇列的頭部恰好是nums[i-1],則將其從佇列裡刪除
- 比較nums[j]與隊列當中的數字(從後往前看),將小於nums[j]的全部刪除(因為nums[j]是當前新加入的數字,比它小的絕不可能是視窗最大值)
- 將nums[j]加入到佇列尾部,這時整個佇列是非嚴格遞減的,頭部的值就是當前視窗的最大值
- 如果i >= 0,將頭部數字加入到res中
總結起來,就是維護一個特殊的雙端佇列資料結構,視窗滑動時,將上個視窗的第一個元素刪除,再把上個視窗之後的第一個元素加入進來,且保證佇列頭部數字最大。
細節問題:
- 指標初始值:
j = 0,i = 0-(k - 1) = 1-k
- 結果陣列res的長度:從第k個元素開始,到第n個元素,有幾個數字?n-k+1
- java的雙端佇列:
Deque<Integer> deque = new LinkedList<>();
- peekFirst(),peekLast()
- removeFirst(),removeLast()
- addLast
- 特殊情況:空陣列,k = 0,返回空陣列
- 刪除元素的時候,必須保證佇列非空
解答
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