1. 程式人生 > >Leetcode 239.滑動視窗最大值

Leetcode 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

注意:

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

進階:

你能線上性時間複雜度內解決此題嗎?

思路

維護一個大小為K的最大堆,依此維護一個大小為K的視窗,每次讀入一個新數,都把堆中視窗最左邊的數扔掉,再把新數加入堆中,這樣堆頂就是這個視窗內最大的值。

注意

-結果陣列的大小是nums.length + 1 - k, 賦值時下標也是i + 1 - k

 1 import
java.util.Collections; 2 import java.util.PriorityQueue; 3 4 public class Solution { 5 public int[] maxSlidingWindow(int[] nums, int k) { 6 if(nums == null || nums.length == 0) return new int[0]; 7 PriorityQueue<Integer> pq = new PriorityQueue<Integer>(Collections.reverseOrder());
8 int[] res = new int[nums.length + 1 - k]; 9 for(int i = 0; i < nums.length; i++){ 10 // 把視窗最左邊的數去掉 11 if(i >= k) pq.remove(nums[i - k]); 12 // 把新的數加入視窗的堆中 13 pq.offer(nums[i]); 14 // 堆頂就是視窗的最大值 15 if(i + 1 >= k) res[i + 1 - k] = pq.peek(); 16 } 17 return res; 18 } 19 }

 

雙向佇列

複雜度

時間 O(N) 空間 O(K)

思路

我們用雙向佇列可以在O(N)時間內解決這題。當我們遇到新的數時,將新的數和雙向佇列的末尾比較,如果末尾比新數小,則把末尾扔掉,直到該佇列的末尾比新數大或者佇列為空的時候才住手。這樣,我們可以保證佇列裡的元素是從頭到尾降序的,由於佇列裡只有視窗內的數,所以他們其實就是視窗內第一大,第二大,第三大...的數。保持佇列裡只有視窗內數的方法和上個解法一樣,也是每來一個新的把視窗最左邊的扔掉,然後把新的加進去。然而由於我們在加新數的時候,已經把很多沒用的數給扔了,這樣佇列頭部的數並不一定是視窗最左邊的數。這裡的技巧是,我們佇列中存的是那個數在原陣列中的下標,這樣我們既可以直到這個數的值,也可以知道該數是不是視窗最左邊的數。這裡為什麼時間複雜度是O(N)呢?因為每個數只可能被操作最多兩次,一次是加入佇列的時候,一次是因為有別的更大數在後面,所以被扔掉,或者因為出了視窗而被扔掉。

 1 public class Solution {
 2     public int[] maxSlidingWindow(int[] nums, int k) {
 3         if(nums == null || nums.length == 0) return new int[0];
 4         LinkedList<Integer> deque = new LinkedList<Integer>();
 5         int[] res = new int[nums.length + 1 - k];
 6         for(int i = 0; i < nums.length; i++){
 7             // 每當新數進來時,如果發現佇列頭部的數的下標,是視窗最左邊數的下標,則扔掉
 8             if(!deque.isEmpty() && deque.peekFirst() == i - k) deque.poll();
 9             // 把佇列尾部所有比新數小的都扔掉,保證佇列是降序的
10             while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) deque.removeLast();
11             // 加入新數
12             deque.offerLast(i);
13             // 佇列頭部就是該視窗內第一大的
14             if((i + 1) >= k) res[i + 1 - k] = nums[deque.peek()];
15         }
16         return res;
17     }
18 }