[劍指offer題解][Java]隊列的最大值/滑動窗口的最大值

前言

衆所周知,《劍指offer》是一本「好書」。html

爲何這麼說?java

由於在技術面試中,它裏面羅列的算法題在面試中出現的頻率是很是很是高的。git

有多高,以我目前很少的面試來看,在全部遇到的面試算法題中,出現原題的機率大概能有6成,若是把基於原題的變種題目算上,那麼這個出現機率能到達9成,10題中9題見過。github

至於爲何給「好書」這兩個字打引號,由於這本書成了面試官的必備,若是考生不會這本書上的題目,就極可能獲得面試官負面的評價。這本書快要成爲評判學生算法能力的惟一標準,這使得考前突擊變成了一個慣例,反而讓投機倒把成了必要,並不必定能真正的考察考生的算法能力。面試

對於劍指offer題解這個系列,個人寫文章思路是,對於看了文章的讀者,可以:算法

  • 迅速瞭解該題常看法答思路(奇技淫巧不包括在內,節省你們時間,實在有研究需求的人能夠查閱其它資料)
  • 思路儘可能貼近原書(例如書中提到的面試官常常會要求不改變原數組,或者有空間限制等,儘可能體如今代碼中,保證讀者能夠不漏掉書中細節)
  • 儘可能精簡話語,避免冗長解釋
  • 給出代碼可運行,註釋齊全,對細節進行解釋

快速找到個人《劍指offer題解》專欄:後端

  • 公衆號(Rude3Knife):底部導航欄——劍指offer題解
  • CSDN(@Rude3Knife):劍指offer題解專欄

題目介紹

劍指offer面試題59題數組

給定一個數組和滑動窗口的大小,找出全部滑動窗口裏數值的最大值。例如,若是輸入數組{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]}。安全

解題思路

蠻力法

思路

掃描窗口k,獲得最大值。對於長度爲n的數組,算法時間複雜度O(nk)bash

顯然不是最優解。

用兩個棧實現隊列

思路

面試題30中,咱們實現過用兩個棧實現了隊列,能夠在O(1)時間獲得棧的最大值,也就能夠獲得隊列的最大值。

這樣總的時間複雜度O(n)

可是這樣的思路寫代碼,等於同時要寫兩個題目,面試時間可能不容許。

雙端隊列

思路

參考解釋:

cuijiahua.com/blog/2018/0…

數組的第一個數字是2,把它存入隊列中。第二個數字是3,比2大,因此2不多是滑動窗口中的最大值,所以把2從隊列裏刪除,再把3存入隊列中。第三個數字是4,比3大,一樣的刪3存4。此時滑動窗口中已經有3個數字,而它的最大值4位於隊列的頭部。

第四個數字2比4小,可是當4滑出以後它仍是有可能成爲最大值的,因此咱們把2存入隊列的尾部。下一個數字是6,比4和2都大,刪4和2,存6。就這樣依次進行,最大值永遠位於隊列的頭部。

可是咱們怎樣判斷滑動窗口是否包括一個數字?應該在隊列裏存入數字在數組裏的下標,而不是數值。當一個數字的下標與當前處理的數字的下標之差大於或者相等於滑動窗口大小時,這個數字已經從窗口中滑出,能夠從隊列頭部把它刪除。所以,咱們既有可能從頭部刪除數字,又可能從尾部刪除數字,因此要雙端隊列。

代碼

注意點:

  • ArrayDeque的幾個API:pollFirst、peekFirst等

  • ArrayDeque保存的是下標

  • 最新的數的下標是一定加進去的。

import java.util.ArrayList;
import java.util.ArrayDeque;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> result = new ArrayList<>();
        // 排除特殊狀況,窗口的長度爲0
        if (size==0) return result;
        
        // 滑動窗口最左邊數的index
        int begin;
        // 創建一個雙端隊列
        ArrayDeque<Integer> q = new ArrayDeque<>();
        for(int i=0;i<num.length;i++){
            // begin是窗口起始位置
            begin = i-size+1;
            // 隊列空,直接加入
            if(q.isEmpty())
                q.add(i);
            // 若隊列最左邊值已經不在窗口內,直接刪除
            else if(begin > q.peekFirst())
                q.pollFirst();
            
            // 從隊尾開始比較,把全部比他小的值丟掉
            while((!q.isEmpty()) && num[q.peekLast()] <= num[i])
                q.pollLast();
            // 隨後再把它放進去
            q.add(i);
            
            // 若窗口起始位置在數組的0位置上或者以後(窗口是完整大小的),才計算窗口的有效最大值
            if(begin>=0){
                // 永遠是隊列最左邊最大,加入結果集
                result.add(num[q.peekFirst()]);
            }
        }
        return result;
    }
}
複製代碼

總結

採用雙端隊列,很是巧妙地一題。

關注我

我目前是一名後端開發工程師。技術領域主要關注後端開發,數據爬蟲,數據安全,5G,物聯網等方向。

微信:yangzd1102

Github:@qqxx6661

我的博客:

原創博客主要內容

  • Java知識點複習全手冊
  • Leetcode算法題解析
  • 劍指offer算法題解析
  • SpringCloud菜鳥入門實戰系列
  • SpringBoot菜鳥入門實戰系列
  • Python爬蟲相關技術文章
  • 後端開發相關技術文章

我的公衆號:Rude3Knife

我的公衆號:Rude3Knife

若是文章對你有幫助,不妨收藏起來並轉發給您的朋友們~

相關文章
相關標籤/搜索