原題地址:?. - 力扣(LeetCode)
給你一個整數數組?nums
,有一個大小為?k
?的滑動窗口從數組的最左側移動到數組的最右側。你只可以看到在滑動窗口內的?k
?個數字。滑動窗口每次只向右移動一位。
返回?滑動窗口中的最大值?。
示例 1:
輸入: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 31 [3 -1 -3] 5 3 6 7 31 3 [-1 -3 5] 3 6 7 51 3 -1 [-3 5 3] 6 7 51 3 -1 -3 [5 3 6] 7 61 3 -1 -3 5 [3 6 7] 7
示例 2:
輸入:nums = [1], k = 1 輸出:[1]
暴力法
這道題相比較其他題目可以說簡單易懂,找到滑動窗口中最大值就行了。先定義一個結果數組result,長度是nums.length - k + 1,再確定遍歷的數組大小是0到nums.length - k,最后遍歷[i,i+k]的窗口,找到最大值存入result中就行了。
public int[] maxSlidingWindow(int[] nums, int k) {// 結果數組,存放結果int[] result = new int[nums.length - k + 1];for (int i = 0; i <= nums.length - k; i++) {int max = nums[i];for (int j = i; j < i + k; j++) {if (nums[j] > max) {max = nums[j];}}result[i] = max;}return result;
}
?但是我們來看下這道題的難度,是困難級別,可想而知不會這么簡單
果然跑到40個用例的時候直接超出時間限制了,這時的滑動窗口寬度是26779.
雙向隊列?
我們發現,窗口在滑動過程中,其實數據發生的變化很小:只有第一個元素被刪除、后面又新增一個元素,中間的大量元素是不變的。也就是說,前后兩個窗口中,有大量數據是 重疊 的。
[1, 3, -1,] -3, 5, 3, 6, 7
1, [3, -1, -3,] 5, 3, 6, 7
1, 3, [-1, -3, 5,] 3, 6, 7
自然想到,其實可以使用一個 隊列 來保存窗口數據:窗口每次滑動,我們就讓后面的一個元素(-3)進隊,并且讓第一個元素(1)出隊。進出隊列的操作,只要耗費常數時間。
這種場景,可以使用 雙向隊列(也叫雙端隊列Dequeue),該數據結構可以從兩端以常數時間壓入/彈出元素。
在構建雙向隊列的時候,可以采用刪除隊尾更小元素的策略,所以,得到的其實就是一個 從大到小排序 的隊列。
這樣存儲的元素,可以認為是遵循“更新更大”原則的。
public int[] maxSlidingWindow1(int[] nums, int k) {if (k == 1) return nums;// 結果數組,存放結果int[] result = new int[nums.length - k + 1];Deque<Integer> deque = new ArrayDeque<>();// 初始化隊列,隊列里面記錄indexfor (int i = 0; i < k; i++) {// 比較將要放入的數與隊列最后一個數,如果小于將要放入的數則刪除// 最后會得到一個從大到小排列的隊列while (!deque.isEmpty() && nums[i] > nums[deque.getLast()]) {deque.removeLast();}deque.addLast(i);}// 第一個數最大result[0] = nums[deque.getFirst()];for (int i = k; i < nums.length; i++) {// 判斷隊列中第一個是否要出窗口if (i - k == deque.getFirst()) deque.removeFirst();// 入隊列while (!deque.isEmpty() && nums[i] > nums[deque.getLast()]) {deque.removeLast();}deque.addLast(i);result[i - k + 1] = nums[deque.getFirst()];}return result;
}