LeetCode 2090:半径为 k 的子数组平均值
-
- [1. 题目介绍](#1. 题目介绍)
- [2. 解题思路](#2. 解题思路)
- [3. 示例代码](#3. 示例代码)
-
- [3.1 解法一:暴力枚举](#3.1 解法一:暴力枚举)
- [3.2 解法二:滑动窗口](#3.2 解法二:滑动窗口)
- [4. 总结](#4. 总结)

🎬 博主名称: 超级苦力怕
🔥 个人专栏: 《LeetCode 题解》
🚀 每一次思考都是突破的前奏,每一次复盘都是精进的开始!
本篇文章讲解的是 LeetCode 第 2090 题------半径为 k 的子数组平均值 。这是一道典型的 定长滑动窗口 题目,题目要求我们以每个位置为中心,计算长度固定为
2k+1的区间平均值。本文将使用 Java 进行讲解,从暴力枚举出发,逐步过渡到滑动窗口,帮助你掌握这类"固定长度区间求和再计算答案"的常见解法。
1. 题目介绍
2090. 半径为 k 的子数组平均值
直达链接:LeetCode 2090
给你一个下标从 0 开始的整数数组 nums,以及一个整数 k。
对于每个下标 i,如果它左边和右边都至少还有 k 个元素,那么就可以得到一个以下标 i 为中心、长度为 2k+1 的子数组,其平均值就是对应位置的结果;否则该位置结果为 -1。
你需要返回一个长度为 n 的数组 avgs,其中 avgs[i] 表示下标 i 的半径为 k 的子数组平均值。注意这里使用的是 截断式整数除法。

2. 解题思路
题目要求我们对每个位置 i,计算一个固定长度为 2k+1 的子数组平均值。
也就是说,只有当区间 [i-k, i+k] 合法存在时,才能计算答案;否则该位置直接记为 -1。
由于窗口长度固定为 2k+1,我们不需要每次都重新统计整段区间的元素和,而是可以维护一个 定长滑动窗口。
设当前窗口元素和为 sum,那么窗口对应的中心位置就是当前右端点减去 k,即:
center = i - k
当窗口长度达到 2k+1 时:
- 当前中心位置的答案可以直接由
sum / (2k+1)得到 - 然后把窗口最左侧元素移出,为下一个位置做准备
解法一:暴力枚举
算法思想:
- 枚举每一个下标
i - 判断它左右两侧是否都至少还有
k个元素 - 如果可以形成长度为
2k+1的区间,就遍历该区间求和 - 最后计算平均值并写入答案数组,否则记为
-1
复杂度分析:
- 时间复杂度:O(n * (2k+1)),最坏情况下每个位置都要重新遍历整个区间
- 空间复杂度:O(1),不计答案数组的额外空间
解法二:滑动窗口(推荐)
通过维护一个长度为 2k+1 的滑动窗口,我们就能在窗口右移时高效更新区间和。
实现思路:
- 先让元素不断进入窗口
- 当窗口大小不足
2k+1时,先继续扩张窗口 - 当窗口大小达到
2k+1时,就可以计算当前中心位置的平均值 - 然后移出窗口最左侧元素,让窗口继续向右滑动
这样每个元素最多只会进入窗口一次、移出窗口一次,因此整体时间复杂度可以优化到 O(n)。
示例推演 (以示例 1 为例:nums = [7,4,3,9,1,8,5,2,6],k = 3)
窗口长度 = 2k + 1 = 7
1. 先累加前 7 个元素,形成窗口 [0,6]
- 窗口内容:[7,4,3,9,1,8,5]
- 元素和 sum = 37
- 中心位置 = 3
- avgs[3] = 37 / 7 = 5
2. 窗口右移一位,变成 [1,7]
- 移出 7,加入 2
- 新窗口内容:[4,3,9,1,8,5,2]
- 元素和 sum = 32
- 中心位置 = 4
- avgs[4] = 32 / 7 = 4
3. 窗口继续右移一位,变成 [2,8]
- 移出 4,加入 6
- 新窗口内容:[3,9,1,8,5,2,6]
- 元素和 sum = 34
- 中心位置 = 5
- avgs[5] = 34 / 7 = 4
4. 其余位置由于左右元素不足 k 个
- 结果保持为 -1
最终答案:[-1,-1,-1,5,4,4,-1,-1,-1]
复杂度分析
- 时间复杂度:O(n),每个元素至多参与一次加入窗口和一次移出窗口
- 空间复杂度:O(1),不计答案数组的额外空间
- 相比暴力枚举,滑动窗口避免了对固定区间的重复求和
3. 示例代码
3.1 解法一:暴力枚举
Java 实现:
java
class Solution {
public int[] getAverages(int[] nums, int k) {
int n = nums.length;
int[] avgs = new int[n];
for (int i = 0; i < n; i++) {
if (i - k < 0 || i + k >= n) {
avgs[i] = -1;
continue;
}
long sum = 0;
for (int j = i - k; j <= i + k; j++) {
sum += nums[j];
}
avgs[i] = (int) (sum / (k * 2 + 1));
}
return avgs;
}
}
3.2 解法二:滑动窗口
核心思想 :维护一个长度为 2k+1 的窗口,遍历数组时动态更新窗口元素和,并在窗口长度达到要求后记录中心位置的答案。
java
import java.util.Arrays;
class Solution {
public int[] getAverages(int[] nums, int k) {
int n = nums.length;
int[] avgs = new int[n];
Arrays.fill(avgs, -1);
// s:记录当前窗口内的元素和,注意这里必须使用 long
long s = 0;
// 遍历数组,枚举每个可能成为窗口右端点的位置 i
for (int i = 0; i < n; i++) {
// ===== 步骤 1:将当前右端点元素加入窗口 =====
s += nums[i];
// 如果窗口大小还不足 2k+1,则继续扩张窗口
if (i < k * 2) {
continue;
}
// ===== 步骤 2:记录答案 =====
// 当前窗口对应的中心位置是 i-k
avgs[i - k] = (int) (s / (k * 2 + 1));
// ===== 步骤 3:将窗口最左侧元素移出 =====
s -= nums[i - k * 2];
}
return avgs;
}
}
更简洁的理解:
- 窗口右端点是
i - 当
i >= 2k时,说明长度为2k+1的窗口已经形成 - 此时中心位置就是
i-k - 记录答案后,移除窗口左端元素即可
java
import java.util.Arrays;
class Solution {
public int[] getAverages(int[] nums, int k) {
int n = nums.length;
int[] avgs = new int[n];
Arrays.fill(avgs, -1);
long sum = 0;
int len = k * 2 + 1;
for (int i = 0; i < n; i++) {
sum += nums[i];
if (i < len - 1) {
continue;
}
avgs[i - k] = (int) (sum / len);
sum -= nums[i - len + 1];
}
return avgs;
}
}
4. 总结
| 解法 | 时间复杂度 | 空间复杂度 | 推荐指数 |
|---|---|---|---|
| 滑动窗口 | O(n) | O(1) | ⭐⭐⭐⭐⭐ |
| 暴力枚举 | O(n * (2k+1)) | O(1) | ⭐⭐⭐ |
核心要点:
- 本题的关键是识别出"以某个位置为中心的区间",本质上对应一个固定长度为
2k+1的窗口 - 当窗口长度固定时,优先考虑使用滑动窗口来维护区间和
- 由于区间元素和可能较大,代码中要使用
long来避免溢出
