劍指offer(59)——滑動視窗的最大值

YaDe.發表於2020-12-24

一、題目描述

給定一個陣列 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

二、解題思路

在這裡插入圖片描述

輸出陣列大小計算,假設陣列長度為n,視窗大小為k,則陣列長度為 n - k + 1,其中n-k為視窗可以移動的距離,然後需要加上視窗本身。如圖所示,陣列長度為8,視窗大小為3,則輸出的陣列大小為6。

1、思路一:暴力法

按照題目要求找到視窗的邊界,在視窗中找到最大值然後移動視窗。

1.1 程式碼實現

public int[] maxSlidingWindow(int[] nums, int k) {
    if(nums.length < k || k == 0 || nums.length == 0)
        return new int[0];
    int[] res = new int[nums.length-k+1];
    int index = 0;
    for(int i = k-1;i<nums.length;i++){
        int temp = - Integer.MAX_VALUE,j=0,n=i;
        while(j<k){
            temp = Math.max(temp,nums[n--]);
            j++;
        }
        res[index++] = temp;
    }
    return res;
}

1.2 複雜度分析

這種演算法時間複雜度很高,需要比較 (n-k+1)*k 次

  • 時間複雜度:O(n*k),k為滑動視窗的大小
  • 空間複雜度:O(1),輸出說需要的陣列不算,需要常數級的空間

2、思路二:雙端佇列

思路一中每個滑動視窗找最大值都要全部比較一次,上一次找到的最大值懟於下一次找最大值沒有幫助,這是我們對思路的演算法進行優化的切入點。

2.1 演算法思路

採用雙端佇列,維護一個單調佇列,使得滑動視窗每前進一步,原佇列的佇列頭為上個視窗的最大值,前進一步後,視窗中的每一個值都與佇列中的隊尾元素進行比較,刪除佇列中所有比當前元素小的值,然後插入該元素,繼續遍歷下一個元素,最後佇列的頭一定是佇列中最大的元素。

2.2 程式碼實現

public int[] maxSlidingWindow(int[] nums, int k) {
    int len = nums.length;
    if(len == 0 || k==0 || len < k){
        return nums;
    }
    Deque<Integer> deque = new LinkedList<>();
    //陣列長度減去視窗大小為視窗移動的距離
    int[] res = new int[len - k + 1];
    //陣列的下標
    int index = 0;
    //未形成視窗區間
    for(int i = 0;i < k;i++){
        //佇列不為空,當前值與佇列尾部比較,如果大於,刪除佇列尾部值
        //一直迴圈刪除到佇列中的值都大於當前值,或者刪除到佇列為空
        //即刪除佇列中小於當前值的數
        while(!deque.isEmpty() && nums[i] > deque.getLast()){
            deque.removeLast();
        }
        //執行完上面的迴圈後,佇列中要麼為空,要麼都比當前值大,然後就把當前值新增到佇列尾部
        deque.addLast(nums[i]);
    }
    //視窗區間剛形成後,把佇列首位值新增到佇列中
    //因為視窗形成後,就需要把佇列首位新增到陣列中,下面的迴圈跳過了這一步
    res[index++] = deque.getFirst();
    //視窗區間形成
    for(int i = k;i < len;i++){
        //i-k已經是區間外了,如果首位等於nums[i-k](首位為最大的數),
        //那麼說明此時首位已經不在區間內了,需要刪除
        if(deque.getFirst() == nums[i - k]){
            deque.removeFirst();
        }
        //刪除佇列中比當前值小的數
        while(!deque.isEmpty() && nums[i] > deque.getLast()){
            deque.removeLast();
        }
        deque.addLast(nums[i]);
        res[index++] = deque.getFirst();
    }
    return res;
}

2.3 複雜度分析

  • 時間複雜度:O(n),遍歷陣列nums一次需要O(n);每個元素最多僅入隊出隊一次,單調佇列佔用O(2n)。
  • 空間複雜度:O(k),雙端佇列中最多同時存在k個元素(k為視窗大小)。

相關文章