法算day11

法算day11
lief
[up主专用,视频内嵌代码贴在这]
滑动窗口最大值
问题描述
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:1
2
3
4
5
6
7
8滑动窗口的位置 最大值
--------------- -----
[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 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
核心思路
这个算法使用双端队列(Deque)来高效地找到每个滑动窗口的最大值:
- 队列中存储的是数组元素的索引,而不是元素值本身
- 队列中的索引对应的元素值是递减的
- 队列头部始终是当前窗口的最大值的索引
语法解析
Deque<Integer> deque = new LinkedList<>();
- Deque
: 接口类型声明,表示这是一个双端队列,存储整数类型 - deque: 变量名
- new LinkedList<>(): 创建LinkedList类的实例作为Deque的实现
: 泛型,指定队列中存储的元素类型为整数
实际内存结构
- 在内存中,LinkedList 实现的Deque大致如下:
1
2
3deque → [头节点] ↔ [节点1] ↔ [节点2] ↔ ... ↔ [尾节点]
↓ ↓ ↓ ↓
索引 索引 索引 索引
Deque的常用方法
- 队首操作
1
2
3
4
5
6deque.addFirst(element); // 在队首添加元素
deque.offerFirst(element); // 在队首添加元素(推荐)
deque.removeFirst(); // 移除并返回队首元素
deque.pollFirst(); // 移除并返回队首元素(推荐)
deque.getFirst(); // 获取队首元素(不移除)
deque.peekFirst(); // 获取队首元素(不移除,推荐) - 队尾操作
1
2
3
4
5
6deque.addLast(element); // 在队尾添加元素
deque.offerLast(element); // 在队尾添加元素(推荐)
deque.removeLast(); // 移除并返回队尾元素
deque.pollLast(); // 移除并返回队尾元素(推荐)
deque.getLast(); // 获取队尾元素(不移除)
deque.peekLast(); // 获取队尾元素(不移除,推荐)
java版
1 | import java.util.*; |
✅ 说明:
- 时间复杂度O(n)
- 空间复杂度O(k)
滑动窗口移除逻辑详细示例说明nums1 = {1, 3, -1, -3, 5, 3, 6, 7};k1 = 3;
初始状态
1
2队列 dq: [] (空)
结果 result: []步骤1: i=0, nums[0]=1
1
2
3
4
5
6
7
8
9
10当前元素: 1
窗口: [1] (大小不足k,不记录结果)
操作:
1. 队列为空,直接添加索引0
2. dq = [0] → 对应值 [1]
解释:
- 队列中只有一个元素,所以它自然是当前窗口的最大值
- 但窗口大小不足k,所以不记录结果步骤2: i=1, nums[1]=3
1
2
3
4
5
6
7
8
9
10
11
12
13当前元素: 3
窗口: [1, 3] (大小不足k,不记录结果)
操作:
1. 检查队首索引0是否在窗口内: 0 <= 1-3? 0 <= -2? 否,保留
2. 从队尾开始比较: nums[0]=1 < 3,移除索引0
3. 添加索引1
4. dq = [1] → 对应值 [3]
解释:
- 3比1大,所以移除1,队列中只保留3
- 当前窗口[1,3]的最大值是3
- 但窗口大小不足k,不记录结果步骤3: i=2, nums[2]=-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14当前元素: -1
窗口: [1, 3, -1] (大小达到k,记录结果)
操作:
1. 检查队首索引1是否在窗口内: 1 <= 2-3? 1 <= -1? 否,保留
2. 从队尾开始比较: nums[1]=3 > -1,不移除,直接添加索引2
3. dq = [1, 2] → 对应值 [3, -1]
4. 记录结果: nums[1] = 3
5. result = [3]
解释:
- 3比-1大,所以保留3在队首,-1添加到队尾
- 队列保持递减: [3, -1]
- 当前窗口[1,3,-1]的最大值是3,由队首元素提供步骤4: i=3, nums[3]=-3
1
2
3
4
5
6
7
8
9
10
11
12
13
14当前元素: -3
窗口: [3, -1, -3] (大小达到k,记录结果)
操作:
1. 检查队首索引1是否在窗口内: 1 <= 3-3? 1 <= 0? 否,保留
2. 从队尾开始比较: nums[2]=-1 > -3,不移除,直接添加索引3
3. dq = [1, 2, 3] → 对应值 [3, -1, -3]
4. 记录结果: nums[1] = 3
5. result = [3, 3]
解释:
- -3比-1小,所以直接添加到队尾
- 队列保持递减: [3, -1, -3]
- 当前窗口[3,-1,-3]的最大值是3,由队首元素提供步骤5: i=4, nums[4]=5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20当前元素: 5
窗口: [-1, -3, 5] (大小达到k,记录结果)
操作:
1. 检查队首索引1是否在窗口内: 1 <= 4-3? 1 <= 1? 是,移除索引1
- 现在 dq = [2, 3] → 对应值 [-1, -3]
2. 从队尾开始比较: nums[3]=-3 < 5,移除索引3
- 现在 dq = [2] → 对应值 [-1]
3. 继续比较: nums[2]=-1 < 5,移除索引2
- 现在 dq = [] (空)
4. 添加索引4
5. dq = [4] → 对应值 [5]
6. 记录结果: nums[4] = 5
7. result = [3, 3, 5]
解释:
- 首先移除不在窗口内的索引1(对应值3)
- 然后移除所有比5小的元素(-1和-3)
- 队列中只剩下5
- 当前窗口[-1,-3,5]的最大值是5,由队首元素提供步骤6: i=5, nums[5]=3
1
2
3
4
5
6
7
8
9
10
11
12
13
14当前元素: 3
窗口: [-3, 5, 3] (大小达到k,记录结果)
操作:
1. 检查队首索引4是否在窗口内: 4 <= 5-3? 4 <= 2? 否,保留
2. 从队尾开始比较: nums[4]=5 > 3,不移除,直接添加索引5
3. dq = [4, 5] → 对应值 [5, 3]
4. 记录结果: nums[4] = 5
5. result = [3, 3, 5, 5]
解释:
- 5比3大,所以保留5在队首,3添加到队尾
- 队列保持递减: [5, 3]
- 当前窗口[-3,5,3]的最大值是5,由队首元素提供步骤7: i=6, nums[6]=6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18当前元素: 6
窗口: [5, 3, 6] (大小达到k,记录结果)
操作:
1. 检查队首索引4是否在窗口内: 4 <= 6-3? 4 <= 3? 否,保留
2. 从队尾开始比较: nums[5]=3 < 6,移除索引5
- 现在 dq = [4] → 对应值 [5]
3. 继续比较: nums[4]=5 < 6,移除索引4
- 现在 dq = [] (空)
4. 添加索引6
5. dq = [6] → 对应值 [6]
6. 记录结果: nums[6] = 6
7. result = [3, 3, 5, 5, 6]
解释:
- 移除所有比6小的元素(5和3)
- 队列中只剩下6
- 当前窗口[5,3,6]的最大值是6,由队首元素提供步骤8: i=7, nums[7]=7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16当前元素: 7
窗口: [3, 6, 7] (大小达到k,记录结果)
操作:
1. 检查队首索引6是否在窗口内: 6 <= 7-3? 6 <= 4? 否,保留
2. 从队尾开始比较: nums[6]=6 < 7,移除索引6
- 现在 dq = [] (空)
3. 添加索引7
4. dq = [7] → 对应值 [7]
5. 记录结果: nums[7] = 7
6. result = [3, 3, 5, 5, 6, 7]
解释:
- 移除所有比7小的元素(6)
- 队列中只剩下7
- 当前窗口[3,6,7]的最大值是7,由队首元素提供
为什么队首总是最大值?
通过这个详细示例,我们可以看到:
- 队列始终保持递减:从队首到队尾,元素值逐渐减小
- 队首元素总是在窗口内:我们定期检查并移除不在窗口内的元素
- 没有更大的元素被遗漏:
如果有更大的元素,它会在加入队列时移除所有比它小的元素
然后它自己成为新的队首
因此,在每一步中,队首元素都代表了当前窗口中的最大值。
这个算法的巧妙之处在于,它通过维护一个单调递减队列,使得我们可以在O(1)时间内获取当前窗口的最大值,而维护队列的均摊时间复杂度也是O(1),整体算法时间复杂度为O(n)。
python版
1 | from collections import deque |
Comment
匿名评论隐私政策









