大小为K且平均值大于等于阈值的子数组数目

题目要求:

给你一个整数数组 arr 和两个整数 k 和 threshold 。
请你返回长度为 k 且平均值大于等于 threshold 的子数组数目。

一、先从暴力思路开始

对于每个长度为K的子数组:

  1. 求和
  2. 算平均值
  3. 判断是否>= 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”
  • “连续子数组”
  • “最大/最小/平均值”

就应该想到滑动窗口。

posted @ 2026-05-24 18:44  ZealousMclaren  阅读(7)  评论(0)    收藏  举报