WELCOME TO Pluto134340小行星

清风湿润,茶烟轻扬。

11.滑动窗口最大值

239. 滑动窗口最大值

给你一个整数数组 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       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

示例 2:

输入:nums = [1], k = 1
输出:[1]

方法1:  优先队列

PriorityQueue<int[]>pq  = new PriorityQueue<int[]>(new Comparator<int[]>();

自定义Comparator实现大顶堆,offer 入堆,poll 出堆,peek堆顶元素

Comparator.compare(a, b)的返回值规则:

  •  return > 0认为 a “大于” b交换 a 和 b 的位置(把 b 放到 a 前面)
  •  return = 0认为 a “等于” b位置不变
  •  return < 0认为 a “小于” b位置不变(a 保持在 b 前面)

代码设计comparator可以实现比较元素和下标。

new Comparator<int[]>() {
    public int compare(int[] pair1, int[] pair2) {
        // 第一步:先比较数值(pair[0]是数值,pair[1]是下标)
        return pair1[0] != pair2[0] // 若新入堆的元素和堆顶元素不同,则比较大小,否则比较下标【下标大的放堆顶————新的放堆顶】
            ? pair2[0] - pair1[0]  // 数值不同时,按数值降序
            : pair2[1] - pair1[1]; // 数值相同时,按下标降序
    }
};

先把前k 个入堆,在遍历k+1~n,逐个入堆更新堆顶,若堆顶元素下标在窗口外则出堆。

这段代码的核心是利用优先队列(大顶堆) 来维护滑动窗口内的元素,堆顶始终是当前堆中的最大值。滑动窗口右移时,新元素入堆;同时检查堆顶元素的下标是否在当前窗口外,若在则弹出,直到堆顶元素属于当前窗口,此时堆顶就是窗口最大值。

 1 class Solution {
 2     public int[] maxSlidingWindow(int[] nums, int k) {
 3         // 1. 获取数组长度
 4         int n = nums.length;
 5         
 6         // 2. 定义优先队列(大顶堆),存储int[] {数值, 下标}
 7         // 自定义Comparator:先按数值降序,数值相同时按下标降序(避免下标小的先出)
 8         PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
 9             public int compare(int[] pair1, int[] pair2) {
10                 // 核心:pair2[0]-pair1[0] 实现降序(大顶堆)
11                 return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
12             }
13         });
14         
15         // 3. 初始化:将第一个窗口的k个元素入堆
16         for (int i = 0; i < k; ++i) {
17             pq.offer(new int[]{nums[i], i}); // Java中用offer()添加元素(比add更安全)
18         }
19         
20         // 4. 初始化结果数组:长度 = 总元素数 - 窗口大小 + 1
21         int[] ans = new int[n - k + 1];
22         ans[0] = pq.peek()[0]; // peek()获取堆顶元素(不弹出),取数值部分
23         
24         // 5. 滑动窗口:处理从第k个元素开始的后续元素
25         for (int i = k; i < n; ++i) {
26             // 5.1 新元素入堆
27             pq.offer(new int[]{nums[i], i});
28             
29             // 5.2 循环检查堆顶元素是否在窗口外(窗口范围:[i-k+1, i])
30             // 若堆顶下标 <= i-k,说明不在窗口内,弹出
31             while (pq.peek()[1] <= i - k) {
32                 pq.poll(); // poll()弹出堆顶元素
33             }
34             
35             // 5.3 堆顶即为当前窗口最大值,存入结果数组对应位置
36             ans[i - k + 1] = pq.peek()[0];
37         }
38         
39         // 6. 返回结果数组
40         return ans;
41     }
42 }
View Code

 

方法2: 单调队列(双端链表)

思路(俗话版):

队尾只要有更年轻(i越靠后)同时还能力更强(数值越大)的,留着其他比它更早入职同时能力却更差的没有什么意义,统统开了;队首的虽然能力最强,但是年龄最大,一旦发现它超过年龄范围(不在滑动窗口的范围内),不用看能力就可以直接开了。

nums[i]入队前,判断是不是队列里最小的,

若nums[i]比原队尾元素大,则移除队尾元素。【为什么这么做?这又属于更新队列里面的“最大”元素,新来的比原来的大,原来的肯定没机会成为最大的(队首)】。若nums[i]比原队尾元素小,则直接插入队尾。最后检查队首元素是否还在窗口内,不在则移除。

【向dque加入元素下标的核心代码】

1             // 因为当前元素更大,窗口内这些小元素不可能成为最大值,直接移除
2             while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
3                 deque.pollLast(); // 弹出队列尾部元素
4             }
5             // 3.2 将当前元素下标加入队列尾部
6             deque.offerLast(i);
7         }

注意:

peekLast()获取队列尾部元素(不弹出)

pollLast()弹出队列尾部元素

offerLast()向队列尾部添加元素

peekFirst()获取队列头部元素(不弹出)

pollFirst()弹出队列头部元素

【deque中加入的是当前窗口中“有潜力”成为队首元素的下标

 1 class Solution {
 2     public int[] maxSlidingWindow(int[] nums, int k) {
 3         // 1. 获取数组长度
 4         int n = nums.length;
 5         
 6         // 2. 定义双端队列Deque,存储数组元素的下标(核心:队列内下标对应数值单调递减)
 7         Deque<Integer> deque = new LinkedList<Integer>();
 8         
 9         // 3. 初始化:处理第一个窗口的k个元素,构建单调队列
10         for (int i = 0; i < k; ++i) {
11             // 3.1 关键:移除队列尾部所有数值 <= 当前元素的下标(保证队列单调递减)
12             // 因为当前元素更大,窗口内这些小元素不可能成为最大值,直接移除
13             while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
14                 deque.pollLast(); // 弹出队列尾部元素
15             }
16             // 3.2 将当前元素下标加入队列尾部
17             deque.offerLast(i);
18         }
19         
20         // 4. 初始化结果数组:第一个窗口的最大值是队列头部下标对应的数值
21         int[] ans = new int[n - k + 1];
22         ans[0] = nums[deque.peekFirst()];
23         
24         // 5. 滑动窗口:处理从第k个元素开始的后续元素
25         for (int i = k; i < n; ++i) {
26             // 5.1 重复步骤3.1:移除尾部所有比当前元素小的下标,保证队列单调递减
27             while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
28                 deque.pollLast();
29             }
30             // 5.2 当前元素下标入队
31             deque.offerLast(i);
32             
33             // 5.3 检查队列头部下标是否在窗口外(窗口范围:[i-k+1, i])
34             // 若头部下标 <= i-k,说明不在窗口内,弹出
35             while (deque.peekFirst() <= i - k) {
36                 deque.pollFirst();
37             }
38             
39             // 5.4 队列头部即为当前窗口最大值的下标,存入结果
40             ans[i - k + 1] = nums[deque.peekFirst()];
41         }
42         
43         // 6. 返回结果数组
44         return ans;
45     }
46 }
View Code

 

posted @ 2026-01-13 10:24  Pluto134340  阅读(3)  评论(0)    收藏  举报