lcMovingWindow - 滑动窗口 1
1 滑动窗口
priority_queue经常用
0480 滑动窗口中位数
1 题目
https://leetcode-cn.com/problems/sliding-window-median/
2 解题思路
- 1 使用一个multiset维护当前窗口,
- 1.1 不使用priority_queue的原因,无法删除元素
- 1.2 不使用map/set的原因,不能含有重复元素
- 2 对于窗口,维护一个中位数指针,注意到中位数指针在每一次窗口移动只会发生几种情况
- 2.1 向左,向右,不动
- 2.2 分类讨论清除即可
1 | class Solution { |
0992 k个不同元素的子数组个数
1 题目
https://leetcode-cn.com/problems/subarrays-with-k-different-integers/submissions/
2 解题思路
- 1 正常思路:
- 1.1 首先窗口是必须的,即为[st, ed],那么保证这个窗口时刻含有k个不同变量,然后求出来每个以ed为结尾的子数组的个数求和即可
- 1.2 那么以ed为结尾的窗口[st, ed]的子数组个数求法,假设k=2,窗口为1,2,1,2,那么以ed为结尾,st就向前移动,直到窗口内的不同元素个数减少到了k-1,此时st移动到第二个2的位置,一共移动了3次,也就是说以ed为结尾的含有k个不同变量的子数组个数为3。
- 1.3 其中的复杂之地在于:如何判断窗口内不同元素的个数,我们采用经典的空间换时间的方法(因为所有元素的值不会大于数组本身长度),用freq[val]记录val出现的次数, 倘若长度不限呢?那就需要使用unordered_map来记录当前窗口所有元素的出现次数,然后每移动一次st需要遍历一遍这个map来判断当前窗口内不同元素的个数,那么整体复杂度为: o(n * k * k)
- 2 官方题解:
- 2.1 不同元素为k的子数组的个数为: 不同元素最多为k的子数组个数 - 不同元素最多为k-1的子数组个数,那么问题转为求不同元素最多为k的一个数组它子数组的个数
- 2.2 求法: 还是滑动窗口的思想,始终保持窗口中最多元素的个数不超过k(方式为每次移动ed,直到第一次超过k,然后移动st直到小于k),然后对于每个ed,ed - st就是以ed为窗口结尾对应的不同元素不超过k的子数组的个数,举个例子:(官方例子):
用具体的例子理解:最多包含 3 种不同整数的子区间 [1, 3, 2, 3] (双指针算法是在左边界固定的前提下,让右边界走到最右边),当前可以确定 1 开始的满足最多包含 3 种不同整数的子区间有 [1]、[1, 3]、[1, 3, 2]、[1, 3, 2, 3]。
1 | class Solution { |
0904 完全水果个数
1 题目
https://leetcode-cn.com/problems/fruit-into-baskets/
2 解题思路
- 1 正常思路:(于0904https://leetcode-cn.com/problems/fruit-into-baskets/实现)
- 1.1 首先窗口是必须的,即为[st, ed],那么保证这个窗口时刻含有k个不同变量,然后求出来每个以ed为结尾的子数组的个数求和即可
- 1.2 那么以ed为结尾的窗口[st, ed]的子数组个数求法,假设k=2,窗口为1,2,1,2,那么以ed为结尾,st就向前移动,直到窗口内的不同元素个数减少到了k-1,此时st移动到第二个2的位置,一共移动了3次,也就是说以ed为结尾的含有k个不同变量的子数组个数为3。
- 1.3 其中的复杂之地在于:如何判断窗口内不同元素的个数,我们采用经典的空间换时间的方法(因为所有元素的值不会大于数组本身长度),用freq[val]记录val出现的次数, 倘若长度不限呢?那就需要使用unordered_map来记录当前窗口所有元素的出现次数,然后每移动一次st需要遍历一遍这个map来判断当前窗口内不同元素的个数,那么整体复杂度为: o(n * k * (log k)) = o(n)
- 2 官方题解:
- 2.1 不同元素为k的子数组的个数为: 不同元素最多为k的子数组个数 - 不同元素最多为k-1的子数组个数,那么问题转为求不同元素最多为k的一个数组它子数组的个数
- 2.2 求法: 还是滑动窗口的思想,始终保持窗口中最多元素的个数不超过k(方式为每次移动ed,直到第一次超过k,然后移动st直到小于k),然后对于每个ed,ed - st就是以ed为窗口结尾对应的不同元素不超过k的子数组的个数,举个例子:(官方例子):
用具体的例子理解:最多包含 3 种不同整数的子区间 [1, 3, 2, 3] (双指针算法是在左边界固定的前提下,让右边界走到最右边),当前可以确定 1 开始的满足最多包含 3 种不同整数的子区间有 [1]、[1, 3]、[1, 3, 2]、[1, 3, 2, 3]。
1 | class Solution { |
0995 执行次数最小: 每次翻转k个让数组全为1
1 题目
https://leetcode-cn.com/problems/minimum-number-of-k-consecutive-bit-flips/
2 解题思路
- 1 正常思路:窗口始终保持k个,去模拟翻转过程,由于需要在窗口内找到翻转后第一个为0的数字,这个复杂度为O(k),所以总复杂度为: O(n*k)
- 2 官方题解:使用差分数组diff,diff[i]表示nums[i]比nums[i-1]多了多少次翻转,那么当前总的翻转次数就为: sum(diff[i]),对于nums[i]而言, sum(diff[i])%2为0则表示nums[i]需要翻转。
1 | class Solution { |
1696 最大结果
1 题目
https://leetcode-cn.com/problems/jump-game-vi/submissions/
2 解题思路
- 1 采用动态规划:dp[i]表示跳到i处的最大收益,如下:搜索i的前k个下标即可
1
2
3
4
5
6
7
8// o(n * k)解法
dp[0] = 0;
dp[1] = nums[0];
for(int i = 1; i < nums.size() + 1; ++i) {
for(int m = 1; m < k + 1 && i - m > 0; ++m) {
dp[i] = max(dp[i], dp[i-m] + nums[i-1]);
}
} - 2 优化上述搜索前k个下标的方案,我们采用优先队列来维护前k个中最大的上一跳:
- 将上述O(n*k)变为O(n*logk),原因是maxHeap的push操作是logN的复杂度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41class Solution {
public:
struct number {
long long idx = 0;
long long val = 0;
number(long long idx, long long val): idx(idx), val(val) {};
// sort descending
bool operator<(const number& b) const {return this->val < b.val;}
};
int maxResult(vector<int>& nums, int k) {
vector<long long> dp(nums.size() + 1, INT_MIN);
// o(n * k)解法
dp[0] = 0;
dp[1] = nums[0];
// for(int i = 1; i < nums.size() + 1; ++i) {
// for(int m = 1; m < k + 1 && i - m > 0; ++m) {
// dp[i] = max(dp[i], dp[i-m] + nums[i-1]);
// }
// }
std::priority_queue<number, std::vector<number>, std::less<number>> maxHeap;
maxHeap.push({1, nums[0]});
// 使用堆优化:
for(int i = 2; i < nums.size() + 1; ++i) {
while(maxHeap.top().idx < i - k) {
maxHeap.pop();
}
dp[i] = maxHeap.top().val + nums[i - 1];
maxHeap.push({i, dp[i]});
// for(int m = 1; m < k + 1 && i - m > 0; ++m) {
// dp[i] = max(dp[i], dp[i-m] + nums[i-1]);
// }
}
return dp[nums.size()];
}
};
- 将上述O(n*k)变为O(n*logk),原因是maxHeap的push操作是logN的复杂度
总结
滑动窗口的最大值:可以使用maxHeap来维护,复杂度logk