LeetCode 1343:大小为 K 且平均值大于等于阈值的子数组数目
-
- [1. 题目介绍](#1. 题目介绍)
- [2. 解题思路](#2. 解题思路)
- [3. 示例代码](#3. 示例代码)
-
- [3.1 解法一:暴力枚举](#3.1 解法一:暴力枚举)
- [3.2 解法二:滑动窗口](#3.2 解法二:滑动窗口)
- [4. 总结](#4. 总结)

🎬 博主名称: 超级苦力怕
🔥 个人专栏: 《LeetCode 题解》
🚀 每一次思考都是突破的前奏,每一次复盘都是精进的开始!
本篇文章讲解的是 LeetCode 第 1343 题------大小为 K 且平均值大于等于阈值的子数组数目 。这是一道典型的 定长滑动窗口 入门题,题目表面上在考察平均值,实质上考察的是如何高效维护长度固定的区间和。
本文将使用 Java 进行讲解,从暴力枚举出发,逐步过渡到滑动窗口,帮助你理解这类题目的常见处理方式。
1. 题目介绍
1343. 大小为 K 且平均值大于等于阈值的子数组数目
直达链接:LeetCode 1343
给你一个整数数组 arr 和两个整数 k 以及 threshold。
请你返回 arr 中长度为 k 且平均值大于等于 threshold 的子数组的数目。

2. 解题思路
题目要求统计所有长度为 k 的子数组中,平均值大于等于 threshold 的个数。
由于子数组长度固定为 k,判断平均值是否达标,其实可以转化为判断元素和是否满足:
sum >= k * threshold
这样就不需要真的去做除法,直接比较整数即可。
解法一:暴力枚举
算法思想:
- 枚举所有长度为
k的子数组 - 对每个子数组遍历计算元素和
- 判断元素和是否大于等于
k * threshold - 统计满足条件的子数组个数
复杂度分析:
- 时间复杂度:O(n * k),需要遍历每个子数组的每个元素
- 空间复杂度:O(1)
解法二:滑动窗口(推荐)
通过维护一个长度为 k 的滑动窗口,每次移动时,只需要更新进入窗口和离开窗口的元素。
实现思路:窗口每向右移动一位,窗口内大部分元素都不变,因此只需:
- 减去离开窗口的元素
- 加上进入窗口的元素
这样就能在 O(1) 的时间完成窗口和的更新,避免重复计算。
示例推演 (以示例 1 为例:arr = [2,2,2,2,5,5,5,8],k = 3,threshold = 4)
以示例1为例:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4
目标:元素和 >= k * threshold = 3 * 4 = 12
1. 初始化窗口 [0,2] -> [2,2,2]
- 元素和 s = 6,不满足条件
2. 窗口向右滑动到 [1,3] -> [2,2,2]
- 进入窗口: 2,s = 8
- 离开窗口: 2,s = 6
- 不满足条件
3. 窗口向右滑动到 [2,4] -> [2,2,5]
- 进入窗口: 5,s = 11
- 离开窗口: 2,s = 9
- 不满足条件
4. 窗口向右滑动到 [3,5] -> [2,5,5]
- 进入窗口: 5,s = 14
- 离开窗口: 2,s = 12
- 满足条件!ans = 1
5. 窗口向右滑动到 [4,6] -> [5,5,5]
- 进入窗口: 5,s = 17
- 离开窗口: 2,s = 15
- 满足条件!ans = 2
6. 窗口向右滑动到 [5,7] -> [5,5,8]
- 进入窗口: 8,s = 23
- 离开窗口: 5,s = 18
- 满足条件!ans = 3
最终答案: ans = 3
复杂度分析
- 时间复杂度:O(n),每个元素至多参与一次加入窗口和一次移出窗口
- 空间复杂度:O(1),只使用常数额外空间
- 相比暴力枚举,滑动窗口避免了对窗口内元素的重复求和
3. 示例代码
3.1 解法一:暴力枚举
Java 实现:
java
class Solution {
public int numOfSubarrays(int[] arr, int k, int threshold) {
// 用于记录满足条件的子数组个数
int ans = 0;
// 目标元素和 = k * threshold
int target = k * threshold;
// 枚举所有长度为 k 的子数组的起始位置 i
// 起始位置范围:0 到 arr.length - k
for (int i = 0; i <= arr.length - k; i++) {
// 统计当前子数组 arr[i, i+k) 的元素和
int sum = 0;
// 遍历当前窗口内的所有元素
for (int j = i; j < i + k; j++) {
sum += arr[j];
}
// 如果元素和 >= 目标值,则计数加一
if (sum >= target) {
ans++;
}
}
// 返回满足条件的子数组个数
return ans;
}
}
3.2 解法二:滑动窗口
核心思想 :维护一个长度为 k 的窗口,遍历数组时动态更新窗口元素和,并在窗口长度达到 k 后判断当前区间是否满足条件。
java
class Solution {
public int numOfSubarrays(int[] arr, int k, int threshold) {
// ans:记录满足条件的子数组个数
int ans = 0;
// s:记录当前窗口内的元素和
int s = 0;
// 遍历数组,枚举每个可能成为窗口右端点的位置 i
// i 的取值范围:0 到 arr.length - 1
for (int i = 0; i < arr.length; i++) {
// ===== 步骤 1:将当前右端点元素加入窗口 =====
s += arr[i];
// 计算当前窗口的左端点位置
// 当 i = k-1 时,left = 0,此时窗口大小正好为 k
int left = i - k + 1;
// 如果左端点位置小于 0,说明窗口大小还不足 k
// 尚未形成第一个完整的窗口,继续下一轮循环
if (left < 0) {
continue;
}
// ===== 步骤 2:更新答案 =====
// 当窗口大小达到 k 时,判断元素和是否满足条件
// 元素和 >= k * threshold 等价于 平均值 >= threshold
if (s >= k * threshold) {
ans++;
}
// ===== 步骤 3:将当前左端点元素移出窗口 =====
// 为下一轮循环做准备,下一轮窗口会向右移动一位
// 记录即将离开窗口的元素
s -= arr[left];
}
// 返回满足条件的子数组个数
return ans;
}
}
更简洁的写法:本质上和上面的实现完全一致,只是把窗口是否形成的判断写得更紧凑一些。
java
class Solution {
public int numOfSubarrays(int[] arr, int k, int threshold) {
int ans = 0;
int s = 0;
for (int i = 0; i < arr.length; i++) {
s += arr[i];
if (i < k - 1) {
continue;
}
if (s >= k * threshold) {
ans++;
}
s -= arr[i - k + 1];
}
return ans;
}
}
4. 总结
| 解法 | 时间复杂度 | 空间复杂度 | 推荐指数 |
|---|---|---|---|
| 滑动窗口 | O(n) | O(1) | ⭐⭐⭐⭐⭐ |
| 暴力枚举 | O(n * k) | O(1) | ⭐⭐⭐ |
核心要点:
- 本题的关键是把"平均值判断"转化为"区间和判断"
- 由于窗口长度固定为
k,适合使用滑动窗口优化 - 当窗口长度达到
k后,再开始统计答案
