目录
一,题目描述
英文描述
中文描述
示例与说明
二,解题思路
1,优先队列
2,单调队列
三,AC代码
Java
优先队列
单调对列
四,解题过程
第一搏
第二搏
第三搏
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
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
参考@力扣官方题解【滑动窗口最大值】
最直观的解法就是采用优先队列。
队列中包括滑动区间内的所有值。每次取值时,判断队头是否属于滑动区间内的元素,若不是则弹出,否则直接返回即可。
为了方便的判断元素位置,可以考虑在优先队列中存放大小为2的数组(位置1:元素值,位置2:元素下标)。
优先队列的作用主要是为了获得滑动窗口内的最大值,然而若只是想获得窗口内最大值的话,维持一个普通队列就可以了。
这样队头的元素一定是最大,且最新的(位于滑动窗口右侧,相较于左侧淘汰的晚些);
可以从上面看出,队列需要支持队头和队尾操作,因此我们采用双端队列。
此外,为了方便的判断元素的位置,队列中存放的值应该为数组中元素对应的下标(有下标后就可以很方便的获得对应的值)。因此队列中存放的值(数组下标)总保持递增的顺序,也就是通常所说的单调队列。
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),要么根据位置来删除。
若堆顶元素的位置不在滑动区间范围内,则将其移除。
因此就需要额外存储元素的位置。
优先队列的作用主要是为了获得滑动窗口内的最大值,然而若只是想获得窗口内最大值的话,维持一个普通队列就可以了。
这样队头的元素一定是最大,且最新的(位于滑动窗口右侧,相较于左侧淘汰的晚些);
可以从上面看出,队列需要支持队头和队尾操作,因此我们采用双端队列。
此外,为了方便的判断元素的位置,队列中存放的值应该为数组中元素对应的下标(有下标后就可以很方便的获得对应的值)。因此队列中存放的值(数组下标)总保持递增的顺序,也就是通常所说的单调队列。