LeetCode 643简单滑动窗口
子数组最大平均数 I 图解题解
这道题到底在问什么
给定整数数组 nums 和整数 k,找出长度恰好为 k 的连续子数组,使它的平均值最大,返回这个最大平均值。
- 输入
- nums = [1,12,-5,-6,50,3], k = 4
- 输出
- 12.75(下标 1~4 的 [12,-5,-6,50] 之和 51,51/4=12.75)
最优解:一步一步想明白
- 3核心就一句:窗口右移一格 = 加新进的、减滑出的。和是边滑边更新的,全程只扫一遍。
- 4先搭起始窗口:把下标 0 到 0 的数加起来,目前窗口和是 1。继续往右凑够 k 个。
- 5先搭起始窗口:把下标 0 到 1 的数加起来,目前窗口和是 13。继续往右凑够 k 个。
- 6先搭起始窗口:把下标 0 到 2 的数加起来,目前窗口和是 8。继续往右凑够 k 个。
- 7先搭起始窗口:把下标 0 到 3 的数加起来,目前窗口和是 2。前 k 个数凑齐了。
- 8起始窗口(高亮这 k 个)的和是 2,先把它当成目前见过的最大窗口和。接下来窗口开始向右滑。
- 9窗口准备向右滑一格。标红的下标 0(值 1)马上要滑出窗口,绿色的下标 4(值 50)马上要进窗口。
- 10窗口滑到下标 1~4。新和 = 旧和 2 + 进的 50 − 出的 1 = 51。这比之前的最大还大,记下它。
- 11窗口准备向右滑一格。标红的下标 1(值 12)马上要滑出窗口,绿色的下标 5(值 3)马上要进窗口。
- 12窗口滑到下标 2~5。新和 = 旧和 51 + 进的 3 − 出的 12 = 42。没超过当前最大 51,最大值不变。
- 13窗口准备向右滑一格。标红的下标 2(值 -5)马上要滑出窗口,绿色的下标 6(值 -2)马上要进窗口。
- 14窗口滑到下标 3~6。新和 = 旧和 42 + 进的 -2 − 出的 -5 = 45。没超过当前最大 51,最大值不变。
- 15窗口准备向右滑一格。标红的下标 3(值 -6)马上要滑出窗口,绿色的下标 7(值 7)马上要进窗口。
- 16窗口滑到下标 4~7。新和 = 旧和 45 + 进的 7 − 出的 -6 = 58。这比之前的最大还大,记下它。
- 17窗口准备向右滑一格。标红的下标 4(值 50)马上要滑出窗口,绿色的下标 8(值 4)马上要进窗口。
- 18窗口滑到下标 5~8。新和 = 旧和 58 + 进的 4 − 出的 50 = 12。没超过当前最大 58,最大值不变。
- 19窗口准备向右滑一格。标红的下标 5(值 3)马上要滑出窗口,绿色的下标 9(值 -1)马上要进窗口。
- 20窗口滑到下标 6~9。新和 = 旧和 12 + 进的 -1 − 出的 3 = 8。没超过当前最大 58,最大值不变。
- 21窗口准备向右滑一格。标红的下标 6(值 -2)马上要滑出窗口,绿色的下标 10(值 8)马上要进窗口。
- 22窗口滑到下标 7~10。新和 = 旧和 8 + 进的 8 − 出的 -2 = 18。没超过当前最大 58,最大值不变。
- 23窗口准备向右滑一格。标红的下标 7(值 7)马上要滑出窗口,绿色的下标 11(值 2)马上要进窗口。
- 24窗口滑到下标 8~11。新和 = 旧和 18 + 进的 2 − 出的 7 = 13。没超过当前最大 58,最大值不变。
- 25滑完整趟,所有长度 4 的窗口里和最大的是高亮这一段(和 58)。最大平均 = 58 ÷ 4 = 14.5,就是答案。
⚠️ 容易写错的地方
✗ 错:每个窗口都从头重新累加求和
✓ 对:用「加新进的、减滑出的」增量更新窗口和
重新累加是 O(k),n 个窗口就是 O(n·k);增量更新让每步只花 O(1)
✗ 错:比较时直接比平均值、反复做除法
✓ 对:窗口里比「和」的大小就行,最后只除一次
k 固定,和最大就等于平均最大;中途比和能避免浮点误差和多余除法
✗ 错:返回时用整数除法
✓ 对:返回 best / k 时确保是浮点除法
和与 k 都是整数,整数相除会截断小数,平均值会算错
完整代码(Python / C++ / Java)
Python
def findMaxAverage(nums, k):
s = sum(nums[:k]) # 起始窗口和
best = s
for r in range(k, len(nums)):
s += nums[r] - nums[r-k] # 加新进的、减滑出的
best = max(best, s)
return best / kC++
double findMaxAverage(vector<int>& nums, int k){
int s = 0;
for (int i = 0; i < k; i++) s += nums[i];
int best = s;
for (int r = k; r < nums.size(); r++) {
s += nums[r] - nums[r-k];
best = max(best, s);
}
return (double)best / k;
}Java
public double findMaxAverage(int[] nums, int k) {
int s = 0;
for (int i = 0; i < k; i++) s += nums[i];
int best = s;
for (int r = k; r < nums.length; r++) {
s += nums[r] - nums[r-k];
best = Math.max(best, s);
}
return (double) best / k;
}复杂度
时间
O(n)
凑起始窗口扫 k 个,之后每滑一格只做一加一减,整体一趟扫过数组
空间
O(1)
只用窗口和 s 与最大值 best 两个变量,不开额外数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 子数组最大平均数 I 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
滑动窗口为什么比暴力快?+
暴力对每个窗口都从头加 k 个数,是 O(n·k)。滑动窗口利用相邻窗口只差「最左和最右」两个数,每滑一格只做一加一减,整体降到 O(n)。
窗口和更新时为什么是「加 nums[r] 减 nums[r-k]」?+
窗口右移一格后,最右进来的是下标 r,最左滑出的是原来的下标 r-k。新和 = 旧和 + 新进的 - 滑出的。
如果数组里有负数会影响做法吗?+
不影响。本题窗口长度固定为 k,不存在「负数要不要收缩窗口」的问题,照样一加一减即可;负数只是让某些窗口和变小而已。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 子数组最大平均数 I 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。