题目链接:239. 滑动窗口最大值
这道题是找每个固定滑动窗口的最大值,开始我的思路是这样的:
-
维护一个大顶堆,用定长滑动窗口的写法,大顶堆的顶部就是最大值,可以直接通过
peek()获取顶部的值,代码如下:javaclass Solution { public int[] maxSlidingWindow(int[] nums, int k) { int mx = Integer.MIN_VALUE; //创建一个大顶堆 PriorityQueue<Integer> pq = new PriorityQueue<>((a,b) -> b - a); int left = 0; List<Integer> ans = new ArrayList<>(); for (int right = 0; right < nums.length; right++){ //入堆 pq.add(nums[right]); //更新最大值 mx = pq.peek(); if (right - left + 1 < k){ continue; } //次数够k了 ans.add(mx); //出堆 pq.remove(nums[left]); left++; } return ans.stream().mapToInt(Integer::intValue).toArray(); } }
但是这个方法是会超时的,超时的原因在于pq.remove(nums[left])需要线性查找元素,时间复杂度是 O(k) 。加上外层循环执行 n 次,总复杂度变为 O(n × k),因此导致了超时
我们可以使用双端队列的写法:
思路如下:
- 维护一个双端队列
- 右边负责入队,并且维护单调性(保证从左到右是递减的)
- 左边负责出队
- 记录答案
具体代码如下:
java
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int []ans = new int[n - k + 1];
//创建队列,存储下标
Deque<Integer> q = new ArrayDeque<>();
for(int i = 0; i < n; i++){
//右边入队,维护单调性
while(!q.isEmpty() && nums[i] >= nums[q.getLast()]){
q.removeLast();
}
q.addLast(i);//保存下标,下面可以判断队首是否离开窗口
//左边出队(窗口最大值就在左边)
int left = i - k + 1;//窗口左边下标
if(q.getFirst() < left){//窗口左边出队,因为窗口每次只滑动 1 步,每轮循环最多只有 1 个 旧下标会移出窗口,所以不需要用while,当然写while也可以过
q.removeFirst();
}
//记录答案
if(left >= 0){
ans[left] = nums[q.getFirst()];
}
}
return ans;
}
}
通过代码我们也不难发现,关键是这两点:
- i跟left负责维护有效窗口,通过判断队首下标是否小于左边界,确保最大值在当前窗口内
- 队列存储下标,负责维护单调性,队首始终是当前窗口的最大值下标
虽然这个方法里面也嵌套了while循环,但是这个方法的时间复杂度不是O(n x k),因为每个下标最多入队出队一次,整个过程中总次数最多不超过2n次,这是线性的,所以总时间复杂度是O(n)
如果这篇文章对你有帮助,欢迎点赞、评论、关注、收藏。你们的支持是我前进的动力!