題目描述:html
給定一個數組和滑動窗口的大小,找出全部滑動窗口裏數值的最大值。例如,若是輸入數組{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]}。java
解題思路:編程
若是不考慮時間開銷,使用蠻力法,本題並不難解決,依次遍歷全部的滑動窗口,掃描每一個窗口中的全部數字並找出其中的最大值,這都很容易實現,可是若是滑動窗口的大小爲k,那麼須要O(k)的時間找最大值,對於長度爲n大的數組,總的時間複雜度爲O(nk)。數組
而後咱們考慮進一步優化,一個滑動窗口實際上能夠當作一個隊列。當窗口滑動時,處於窗口第一個位置的數字被刪除,同時在窗口的末尾又增長了一個新的數字。這符合隊列「先進先出」的特性。函數
在第20題:包含min函數的棧,咱們使用兩個棧實現了一個求最小值的棧,在O(1)時間內能夠獲得最小值,這裏咱們能夠改成求最大值,一樣能夠在O(1)時間內獲得最大值,而這裏的數據用隊列保存,咱們能夠用兩個棧實現隊列,這就是第5題:用兩個棧實現隊列。這樣,實際上綜合這兩題咱們能夠解決本題,總的時間複雜度也能夠降到O(n)。優化
這裏咱們換用另一種方法:使用雙端隊列。咱們不把全部的值都加入滑動窗口,而是隻把有可能成爲最大值的數加入滑動窗口。這就須要一個兩邊均可以操做的雙向隊列。code
咱們以數組{2,3,4,2,6,2,5,1}爲例,滑動窗口大小爲3,先把第一個數字2加入隊列,第二個數字是3,比2大,因此2不多是最大值,因此把2刪除,3存入隊列。第三個數是4,比3大,一樣刪3存4,此時滑動窗口以遍歷三個數字,最大值4在隊列的頭部。htm
第4個數字是2,比隊列中的數字4小,當4滑出去之後,2仍是有可能成爲最大值的,所以將2加入隊列尾部,此時最大值4仍在隊列的頭部。blog
第五個數字是6,隊列的數字4和2都比它小,因此刪掉4和2,將6存入隊列尾部,此時最大值6在隊列頭部。隊列
第六個數字是2,此時隊列中的數6比2大,因此2之後還有多是最大值,因此加入隊列尾部,此時最大值6在仍然隊列頭部。
······
依次進行,這樣每次的最大值都在隊列頭部。
還有一點須要注意的是:若是後面的數字都比前面的小,那麼加入到隊列中的數可能超過窗口大小,這時須要判斷滑動窗口是否包含隊頭的這個元素,爲了進行這個檢查,咱們能夠在隊列中存儲數字在數組中的下標,而不是數值,當一個數字的下標和當前出來的數字下標之差大於等於滑動窗口的大小時,這個元素就應該從隊列中刪除。
舉例:
<div align=center>
</div>
編程實現(Java):
import java.util.*; public class Solution { public ArrayList<Integer> maxInWindows(int [] num, int size){ /* 思路:用雙端隊列實現 */ ArrayList<Integer> res=new ArrayList<>(); if(num==null || num.length<1 || size<=0 || size>num.length) return res; Deque<Integer> queue=new LinkedList<>(); for(int i=0;i<num.length;i++){ while(!queue.isEmpty() && queue.peek()<i-size+1) //超出範圍的去掉 queue.poll(); //當前值大於以前的值,以前的不多是最大值,能夠刪掉 while(!queue.isEmpty() && num[i]>=num[queue.getLast()]) queue.removeLast(); queue.add(i); if(i>=size-1){ //此時開始是第一個滑動窗口 res.add(num[queue.peek()]); } } return res; } }