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

qqxx6661發表於2019-04-22

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

前言

眾所周知,《劍指offer》是一本“好書”。

為什麼這麼說?

因為在技術面試中,它裡面羅列的演算法題在面試中出現的頻率是非常非常高的。

有多高,以我目前不多的面試來看,在所有遇到的面試演算法題中,出現原題的概率大概能有6成,如果把基於原題的變種題目算上,那麼這個出現概率能到達9成,10題中9題見過。

至於為什麼給“好書”這兩個字打引號,因為這本書成了面試官的必備,如果考生不會這本書上的題目,就很可能得到面試官負面的評價。這本書快要成為評判學生演算法能力的唯一標準,這使得考前突擊變成了一個慣例,反而讓投機倒把成了必要,並不一定能真正的考察考生的演算法能力。

對於劍指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)

顯然不是最優解。

用兩個棧實現佇列

思路

面試題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。就這樣依次進行,最大值永遠位於佇列的頭部。

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

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

程式碼

注意點:

  • 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

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

相關文章