滑動窗口最大值
給定一個數(shù)組 nums,有一個大小為 k 的滑動窗口從數(shù)組的最左側(cè)移動到數(shù)組的最右側(cè)。你只可以看到在滑動窗口 k 內(nèi)的數(shù)字?;瑒哟翱诿看沃幌蛴乙苿右晃?。
返回滑動窗口最大值。
示例:
輸入: 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
注意:
你可以假設(shè) k 總是有效的,1 ≤ k ≤ 輸入數(shù)組的大小,且輸入數(shù)組不為空。
進階:
你能在線性時間復(fù)雜度內(nèi)解決此題嗎?
切題
一、Clarification
注意窗口的大小
二、Possible Solution
1、借助大頂堆
這里注意隨著窗口移動,維護大頂堆(加入新元素,刪除離開),最后結(jié)果為堆頂元素。一個元素維護大頂堆的時間復(fù)雜度為O(logk),最后結(jié)果時間復(fù)雜度為O(1),最后的n個元素的時間復(fù)雜度為O(n*logk)
2、雙端隊列
維護一個雙端隊列window其大小為k,從尾入隊列從頭出隊列
Python3實現(xiàn)
雙端隊列
# @author:leacoder
# @des: 雙端隊列 滑動窗口最大值
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
if not nums: return []
length = len(nums)
window ,result = [],[] #window 存在窗口中的數(shù) result用于存最后的結(jié)果
for i, numx in enumerate(nums):
if i>=k-1: # k個元素開始 維護 window
window.append(numx) # 加入新元素
result.append(max(window)) # 取出最大值
window.pop(0) # 刪除下一輪要離開的元素
else: # k - 1元素直接加入 window
window.append(numx)
return result
雙端隊列(優(yōu)化)
優(yōu)化取出最大值的時間
# @author:leacoder
# @des: 雙端隊列 滑動窗口最大值
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
if not nums: return []
window ,result = [],[] #window 存在窗口中的數(shù)的 下標 result用于存最后的結(jié)果
for i, numx in enumerate(nums):
"""
1.判斷元素是否超出滑動窗口范圍
i > k 說明滑動窗口不為空,window[0] < i - k說明最大元素超出了窗口,這時候必須舍棄
隊首元素:window.pop(0)
"""
if i>=k and window[0]<=i-k: #新數(shù)據(jù)來時每次將window最左邊的pop掉window[0]放的是最大而不是最左邊界所以需要判斷
window.pop(0) #不滿足 window內(nèi)條件
while window and nums[window[-1]]<=numx: #維護window 保持k的范圍內(nèi)window最大數(shù)始終在windowp[0]
window.pop() #window中如果有比新進numx小的 pop掉(我們要的是窗口內(nèi)最大)
window.append(i)
#將新進numx 下標加入window window中最大數(shù) 始終是window[0]
#因為上方while循環(huán)已經(jīng)保證在append新進數(shù)時 window中要么為空,新進入數(shù)最大
#要么比新進數(shù)入小的已pop掉留下比新進入數(shù)大的數(shù)放在頭部
if i>=k-1: #下標從0開始 顧 i=k-1時 window中已處理過k個數(shù)了
result.append(nums[window[0]])
return result
'''
巧妙運用了window大小固定,并且 新進入數(shù) 如果比之前window中已有數(shù)都大的話,那么之前的數(shù)永遠不可能是我們需要的數(shù)(滑動窗口最大值)
1 3 -1 -3 5 3 6 7 為nums k=3為例
假設(shè)已到新數(shù)進入前 窗口為 1 [3 -1 -3] 5 3 6 7, 現(xiàn)在新數(shù) 5 下標為四進入,按著上面代碼邏輯
1、pop掉窗口最最左邊數(shù) 3 下標為一 被pop
2、此時window中為[ -1 -3 ]的下標,循環(huán)比較 新數(shù) 5 大于 -1 -3 顧pop掉。此時window為空 跳出循環(huán)
3、將新數(shù) 5下標為四 append入window (存放下標) 此時window為[四] 1 3 [-1 -3 5] 3 6 7
4、這時窗口中最大值 為window[0]為下標的數(shù)
5、新進數(shù)3下標五 進入,-1 已不在window內(nèi)了不需pop,window中只有5下標為四 不需要pop任何數(shù)據(jù),將3 下標五append入window(存放下標) 此時window為[四 五] 1 3 -1 []-3 5 3] 6 7 窗口中最大值 依舊為window[0]為下標的數(shù)
'''
Java 實現(xiàn)
優(yōu)先隊列
/*
@author:leacoder
@des: 優(yōu)先隊列 滑動窗口最大值
PriorityQueue 默認是一個小頂堆
*/
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(k==0){
return nums;
}
PriorityQueue<Integer> queue = new PriorityQueue<>(k, (a, b) -> {return b-a;});//優(yōu)先隊列 頂為最大
for (int i = 0; i < k; i++) {
queue.add(nums[i]);
}
int[] res = new int[nums.length - k + 1];//存放結(jié)果
for (int i = 0; i < res.length; i++) {
res[i] = queue.peek(); //從取優(yōu)先隊列取出最大
queue.remove(nums[i]);//刪除 優(yōu)先隊列中nums[i]
if (i + k < nums.length) {
queue.add(nums[i + k]); //將新進入數(shù) 加入優(yōu)先隊列中
}
}
return res;
}
}
/*
PriorityQueue 默認是一個小頂堆,如何實現(xiàn)大頂堆
1、
PriorityQueue<Integer> pq = new PriorityQueue<>(n,(Integer a,Integer b)->{return b-a;});
2、
PriorityQueue<Integer> pq = new PriorityQueue<>(n, new Comparator<Integer>() {
@Override
public int compare(Integer integer, Integer t1) {
return t1-integer;
}
});
*/
GitHub鏈接:
https://github.com/lichangke/LeetCode
知乎個人首頁:
https://www.zhihu.com/people/lichangke/
個人Blog:
https://lichangke.github.io/
歡迎大家來一起交流學(xué)習(xí)