目录
一,题目描述
英文描述
You are given an array of integers nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.
Return the max sliding window.
中文描述
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
示例与说明
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sliding-window-maximum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
二,解题思路
1,优先队列
最直观的解法就是采用优先队列。
队列中包括滑动区间内的所有值。每次取值时,判断队头是否属于滑动区间内的元素,若不是则弹出,否则直接返回即可。
为了方便的判断元素位置,可以考虑在优先队列中存放大小为2的数组(位置1:元素值,位置2:元素下标)。
2,单调队列
优先队列的作用主要是为了获得滑动窗口内的最大值,然而若只是想获得窗口内最大值的话,维持一个普通队列就可以了。
- 当向队尾插入一个元素时,只需要将队列中小于等于该元素的值从后向前依次弹出即可;
- 若队头元素不属于滑动区间范围内,则从前向后弹出即可;
这样队头的元素一定是最大,且最新的(位于滑动窗口右侧,相较于左侧淘汰的晚些);
可以从上面看出,队列需要支持队头和队尾操作,因此我们采用双端队列。
此外,为了方便的判断元素的位置,队列中存放的值应该为数组中元素对应的下标(有下标后就可以很方便的获得对应的值)。因此队列中存放的值(数组下标)总保持递增的顺序,也就是通常所说的单调队列。
三,AC代码
Java
优先队列
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
// int[2]:位置0表示元素大小,位置1表示元素位置
PriorityQueue<int[]> heap = new PriorityQueue<>(new Comparator<int[]>(){
public int compare(int[] a, int[] b) {
// 元素大小不等时,按照大根堆的方法比较
// 元素大小相等时,位置越小,优先级越高,越先被弹出
return a[0] != b[0] ? b[0] - a[0] : a[1] - b[1];
}
});
int[] ans = new int[nums.length - k + 1];
for (int i = 0; i < k - 1; i++) {
heap.offer(new int[]{nums[i], i});
}
for (int i = k - 1; i < nums.length; i++) {
heap.offer(new int[]{nums[i], i});
while (heap.peek()[1] < i - k + 1) {
heap.poll();
}
ans[i - k + 1] = heap.peek()[0];
}
return ans;
}
}
单调对列
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> queue = new LinkedList<>();
// 将前k-1个数放入队列中
for (int i = 0; i < k - 1; i++) {
while (!queue.isEmpty() && nums[i] >= nums[queue.peekLast()]) {// !!!加上等于 !!!这里需要与队列末尾进行比较,而不是队头
queue.pollLast();
}
queue.offerLast(i);
}
int[] ans = new int[nums.length - k + 1];
// 从k-1开始遍历数组
for (int i = k - 1; i < nums.length; i++) {
// 若队尾元素小于当前元素,则弹出队尾
while (!queue.isEmpty() && nums[i] >= nums[queue.peekLast()]) {// !!!加上等于
queue.pollLast();
}
queue.offerLast(i);
// 将队头超过滑动窗口区间的元素弹出
while (queue.peekFirst() < i - k + 1) {
queue.pollFirst();
}
ans[i - k + 1] = nums[queue.peekFirst()];
}
return ans;
}
}
四,解题过程
第一搏
维护大小为k的大根堆,滑动窗口每移动一格,删除原窗口中第一个元素(heap.remove(nums[i])),将新窗口最后一个元素添加到堆中,返回堆顶即可。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
PriorityQueue<Integer> heap = new PriorityQueue<>(new Comparator<Integer>(){
public int compare(Integer a, Integer b) {
return b - a;// 大根堆
}
});
int[] ans = new int[nums.length - k + 1];
for (int i = 0; i < k - 1; i++) {
heap.offer(nums[i]);
}
for (int i = 0; i + k <= nums.length; i++) {
heap.offer(nums[i + k - 1]);
ans[i] = heap.peek();
heap.remove(nums[i]);
}
return ans;
}
}
第二搏
显然,在堆中查找并删除元素是非常耗时的!
考虑要删除的元素是指滑动窗口第一个值,要么根据值来删除(同上,采用remove),要么根据位置来删除。
若堆顶元素的位置不在滑动区间范围内,则将其移除。
因此就需要额外存储元素的位置。
第三搏
优先队列的作用主要是为了获得滑动窗口内的最大值,然而若只是想获得窗口内最大值的话,维持一个普通队列就可以了。
- 当向队尾插入一个元素时,只需要将队列中小于等于该元素的值从后向前依次弹出即可;
- 若队头元素不属于滑动区间范围内,则从前向后弹出即可;
这样队头的元素一定是最大,且最新的(位于滑动窗口右侧,相较于左侧淘汰的晚些);
可以从上面看出,队列需要支持队头和队尾操作,因此我们采用双端队列。
此外,为了方便的判断元素的位置,队列中存放的值应该为数组中元素对应的下标(有下标后就可以很方便的获得对应的值)。因此队列中存放的值(数组下标)总保持递增的顺序,也就是通常所说的单调队列。