大小为K且平均值大于等于阈值的子数组数目
题目要求:
给你一个整数数组 arr 和两个整数 k 和 threshold 。
请你返回长度为 k 且平均值大于等于 threshold 的子数组数目。
一、先从暴力思路开始
对于每个长度为K的子数组:
- 求和
- 算平均值
- 判断是否>= threshold
例如:
arr = [2,2,2,2,5,5,5,8]
k = 3
长度为3的子数组有:
[2,2,2]
[2,2,2]
[2,2,5]
[2,5,5]
[5,5,5]
[5,5,8]
暴力做法:
每次都重新计算这3个数的和。
二、发现重复计算
比如:
[2,2,5]
下一窗口:
[2,5,5]
实际上:
- 前一个窗口左边出去一个2
- 右边进来一个5
没必要重新求和。
所以:
新窗口和 = 旧窗口和 - 移出去的数 + 新进来的数
这就是滑动窗口。
三、关键优化
平均值:
sum / k >= threshold
等价于:
sum >= k * threshold
这样就不用浮点数了。
四、滑动窗口过程
以:
arr = [2,2,2,2,5,5,5,8]
k = 3
为例。
第一步:先求第一个窗口和
窗口:
[2,2,2]
sum = 6
判断:
6 >= 3 * 4 ?
即:
6 >= 12
不满足。
第二步:窗口右移
旧窗口:
[2,2,2]
新窗口:
[2,2,2]
更新:
sum = 6 - 2 + 2 = 6
继续:
[2,2,5]
sum = 6 - 2 + 5 = 9
继续:
[2,5,5]
sum = 9 - 2 + 5 = 12
满足条件。
五、Java代码
class Solution {
public int numOfSubarrays(int[] arr, int k, int threshold) {
int sum = 0;
// 先计算第一个窗口的和
for (int i = 0; i < k; i++) {
sum += arr[i];
}
int count = 0;
// 判断第一个窗口
if (sum >= k * threshold) {
count++;
}
// 滑动窗口
for (int i = k; i < arr.length; i++) {
// 减去左边移出的元素
sum -= arr[i - k];
// 加上右边新进入的元素
sum += arr[i];
// 判断当前窗口
if (sum >= k * threshold) {
count++;
}
}
return count;
}
}
六、重点理解这一句
sum -= arr[i - k];
很多人这里容易懵。
假设:
i = 5
k = 3
那么当前加入的是:
arr[5]
窗口长度必须保持 3。
所以需要移除:
arr[5 - 3]
= arr[2]
即:
窗口左边那个元素
七、时间复杂度
暴力:
每个窗口都重新求和:
O(n * k)
外层:枚举所有长度为k的子数组,
内层重新计算每个子数组的和
总复杂度 = 窗口个数 × 每个窗口的计算代价
也就是:
O(n × k)
窗口个数一般公式:
n - k + 1
复杂度里忽略常数:
≈ O(n)
滑动窗口:
每次只更新两个数:
O(n)
这就是优化的本质。
八、这类题的统一模板
固定长度滑动窗口:
1. 先算第一个窗口
2. 判断
3. 窗口右移:
减左
加右
判断
以后看到:
- “长度为 k”
- “连续子数组”
- “最大/最小/平均值”
就应该想到滑动窗口。
浙公网安备 33010602011771号