算法入门(一):滑动窗口 之 固定窗口 (Leetcode 643 / 1343 / 2090 )
- 固定窗口
- [模板1(初始化窗口+滑动窗口 -- 双循环)](#模板1(初始化窗口+滑动窗口 -- 双循环))
- 模板2(单循环)
- [LeetCode 643 -- 子数组最大平均数 I](#LeetCode 643 – 子数组最大平均数 I)
- [LeetCode 1343 - 大小为 K 且平均值大于等于阈值的子数组数目](#LeetCode 1343 - 大小为 K 且平均值大于等于阈值的子数组数目)
- [LeetCode 2090 半径为 k 的子数组平均值](#LeetCode 2090 半径为 k 的子数组平均值)
固定窗口
例如Leetcode 643 / 1343 / 2090 这类题目,同属于固定窗口。
- 窗口大小起始为 k
- 每次向右滑动一步,右边加入一个新元素,左边移除一个旧元素
- 始终保持窗口内始终有 k 个元素

模板1(初始化窗口+滑动窗口 -- 双循环)
cpp
int fixedWindow(vector<int>& nums, int k) {
int n = nums.size();
// 0.边界处理
if (n < k) return 0;
// 1. 初始化第一个窗口
int windowSum = 0;
for (int i = 0; i < k; i++) {
windowSum += nums[i];
}
// 2. 滑动窗口获取最大值/最小值
int res = windowSum;
for (int i = k; i < n; i++) {
windowSum = windowSum - nums[i - k] + nums[i];
ans = max(ans, windowSum);
}
return ans;
}
模板2(单循环)
cpp
double fixedWindow(vector<int>& nums, int k) {
int n = nums.size();
double window_sum = 0;
double maxSum = INT_MIN;
for(int i = 0;i<n;i++){
//1. 进入窗口
window_sum +=nums[i];
//2. 窗口不足k时,继续
if(i < k - 1){
continue;
}
//3. 更新答案
maxSum = max(maxSum,window_sum);
//4. 离开当前窗口,去掉左边的元素,为下一个窗口的右边元素做准备
window_sum -=nums[i- k + 1];
}
return maxSum;
}
LeetCode 643 -- 子数组最大平均数 I
固定窗口的母题,只是在求和的基础上加了一步求平均数。
注意答案要求返回double。
写法一(双循环):
cpp
double findMaxAverage(vector<int>& nums, int k) {
int n = nums.size();
double window_sum = INT_MIN;
for(int i = 0;i<k;i++){
window_sum += nums[i];
}
double maxAve = window_sum / k;
for(int i = k;i<n;i++){
window_sum = window_sum - nums[i-k] + nums[i];
maxAve = max(maxAve,window_sum / k);
}
return maxAve;
}
写法二(单循环):
cpp
double findMaxAverage(vector<int>& nums, int k) {
int n = nums.size();
double window_sum = 0;
double maxSum = INT_MIN;//这里一定要写INT_MIN而不是0,因为单循环方法没有初始化窗口!
for(int i = 0;i<n;i++){
window_sum +=nums[i];
if(i < k - 1){
continue;
}
maxSum = max(maxSum,window_sum);
window_sum -=nums[i- k + 1];
}
return maxSum / k;
}
LeetCode 1343 - 大小为 K 且平均值大于等于阈值的子数组数目
LeetCode 1343 - 大小为 K 且平均值大于等于阈值的子数组数目
Leetcode643的进阶版。
- 为每一个窗口添加阈值条件判断
- 注意
窗口平均值 大于等于 阈值,可以转换为窗口总和 大于等于 阈值 * 数目
以下由上题的写法一(双循环)改写而来:
cpp
int numOfSubarrays(vector<int>& arr, int k, int threshold) {
int n = arr.size();
int res = 0;
int window_sum = 0;
for(int i = 0;i<k;i++){
window_sum +=arr[i];
}
if(window_sum >= threshold * k) res++;
for(int i = k;i<n;i++){
window_sum +=arr[i];
window_sum -=arr[i-k];
if(window_sum >= threshold * k) res++;
}
return res;
}
LeetCode 2090 半径为 k 的子数组平均值
这道题相较于上面两道题多了一些思考的部分。
- 相较于单循环,双循环写法,需要判断窗口大小和数组长度的关系,也就是边界处理。
- 为什么前面两道题没有写边界判断?因为题目给了k<n的条件,也就是窗口长度小于数组长度,而这道题没有。
- 该题的数组大小是10^5,需要考虑window_sum的类型,定义为int是否足够?是否需要定义为long long?
写法一(双循环):
cpp
vector<int> getAverages(vector<int>& nums, int k) {
int n = nums.size();
long long window_sum = 0;
vector<int> res(n,-1);
//0. 边界处理
if(2*k + 1 > n) return res;
//1. 第一个窗口
for(int i = 0;i<2*k+1;i++){
window_sum += nums[i];
}
res[k] = window_sum / (2 * k + 1);
//2. 后续的窗口
for(int i = 2*k+1 ;i<n;i++){
window_sum += nums[i];
window_sum -= nums[i - (2*k+1)];
res[i-k] = window_sum / (2 * k + 1);
}
return res;
}
写法二(单循环):
cpp
vector<int> getAverages(vector<int>& nums, int k) {
int n = nums.size();
long long window_sum = 0;
vector<int> res(n,-1);
for(int i = 0;i<n;i++){
window_sum += nums[i];
if(i<2 * k){
continue;
}
res[i-k] = window_sum/(2 * k +1);
window_sum -= nums[i- 2 * k];
}
return res;
}