1. 程式人生 > >LeetCode 239. 滑動視窗最大值 java實現 個人演算法之旅筆記

LeetCode 239. 滑動視窗最大值 java實現 個人演算法之旅筆記

239. 滑動視窗最大值

給定一個數組 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

注意:

你可以假設 總是有效的,1 ≤ k ≤ 輸入陣列的大小,且輸入陣列不為空。

解法一(時間複雜度為O(n*logK)):返回視窗中的的最大值,最大最小值我們可以優先考慮到“優先佇列”,優先佇列用於流式資料的最大最小值。演算法題中,有出現slide window的都是高頻考點

a.維持一個max heap(刪除滑動視窗最左邊的元素,加入新的元素)

b.讓最大值位於大頂堆

a步驟的維持max head的時間複雜度是logK。

b步驟的時間複雜度是N *O(1),即O(N)。

總體時間複雜度為O(n*logK)。

class Solution {

    public int[] maxSlidingWindow(int[] nums, int k) {
                if(!(nums instanceof int[]) || nums == null || nums.length == 0)//判斷傳進來的是否為int陣列,int陣列是否為空,int陣列是否沒有資料
                    return new int[0];
        
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>(Collections.reverseOrder());
        int[] max = new int[nums.length + 1 - k];//nums.length + 1 - k得出該陣列nums裡面滑動視窗的最大值的個數
        for(int i = 0 ;i < nums.length; i++){//遍歷
            //先判斷是否為滑動視窗的最左邊,是的話移出優先佇列
            if(i > k){//i > k ,表示當前滑動窗口裡面有K個數,想要新進來一個數,需要移除滑動視窗最左邊的數
                pq.remove(nums[i -k]);
            }
            pq.offer(nums[i]);//新增資料
            //獲取移動視窗的最大值
            if(i + 1 >= k){//i + 1 >= k,當前滑動窗口裡面有K個數,所有才會有最大值
                max[i + 1 - k] = pq.peek();//表示當前的max的陣列下標i + 1 - k
            }
            
        }
        return max;
        

    }
}

解法二(時間複雜度為O(N)):使用雙端佇列。java中的雙端佇列deque(支援在兩端插入和移除元素)。deque是一個介面,實現它的類有ArrayDeque,LinkedBlockingDeque,LinkedList.

我們用雙向佇列,在遇到新的數的時候,將新的數和雙向佇列的末尾進行比較,如果末尾的數比新數下,則把末尾的數扔掉,直到該佇列的末尾數比新數大或者佇列為空的時候才停止。這樣,我們可以保證佇列裡的元素是重頭到位的降序。由於佇列中只有窗口裡的數,就是窗口裡的第一大數,第二大數,第三數...。

如何保持佇列呢。每當滑動視窗的k已滿,想要新進來一個數,就需要把最滑動視窗最左邊的數移出佇列,新增新的數。

我們在新增新的數的時候,就已經移出了一些數,這樣佇列頭部的數不一定是視窗最左邊的數。技巧:我們佇列中只儲存那個數在原陣列的下標。這樣可以判斷該數是否為最滑動視窗的最左邊的數。

為什麼這個解法的時間複雜度是O(N)呢。因為每個元素在雙端佇列裡有且僅存在過一次。即最多被操作兩次,一次是加入該佇列的時候,一次是因為後面有更大的數而被移除佇列的時候

class Solution {

    public int[] maxSlidingWindow(int[] nums, int k) {
    
        if(!(nums instanceof int[]) || nums == null || nums.length == 0)//判斷傳進來的是否為int陣列,int陣列是否為空,int陣列是否沒有資料
            return new int[0];
        
        ArrayDeque<Integer> adq = new ArrayDeque<Integer>(k);
        int[] max = new int[nums.length + 1 - k];//獲得該nums陣列滑動視窗的個數
        for(int i = 0; i < nums.length; i++){
            //每當新數進來,如果發現佇列的頭部的數的下標是視窗最左邊的下標,則移出佇列
            if(!adq.isEmpty() && adq.peekFirst() == i - k)
                adq.removeFirst();
            //把佇列尾部的數和新數一一比較,比新數小的都移出佇列,直到該佇列的末尾數比新數大或者佇列為空的時候才停止,保證佇列是降序的
            while(!adq.isEmpty() && nums[adq.peekLast()] < nums[i])
                adq.removeLast();
            //從尾部加入新的數
            adq.offerLast(i);
            //佇列頭部就是該視窗最大的數
            if(i + 1 >= k)//i+1 <k時,滑動窗口才有最大值
                max[i + 1 -k] = nums[adq.peek()];
                
        }
        return max;
        
        

    }
}

其實我不知道該怎麼編輯,我只是想把自己學過的寫下來,雖然我很菜,可是我想變強。