LeetCode 346. 数据流中的移动平均值
题目描述
题意分析
固定一个窗口容量
size,每次接收到新整数时,返回最近至多size个输入值的平均数。窗口满后要排除最早进入的数;未满时只对已经收到的数求平均,不能把尚未填入的数据当作零参与分母。调用会持续进行,需要在对象中保留最近数据和相关状态。平均数可能带小数,返回值应按浮点除法计算。当前题型约定容量为正,第一次调用后窗口就至少有一个有效元素。
解法:环形数组与滚动和
核心思路
[!blue]
平均数等于窗口总和除以有效数量,所以每次无需重新扫描全部窗口,只需维护滚动和
sum。加入新值时,把不再属于窗口的最老值扣掉,再加上新值,就能得到更新后的总和。用固定长度数组保存最近的数据,
head指向下一次要写入的槽位。窗口未满时它指向尚未使用的零值槽;满了以后它就指向最早写入、即将被淘汰的元素。写入后令head = (head + 1) % size,到数组末尾会回到开头,按输入先后顺序循环覆盖。因此一次更新可以统一写成“总和加新值减该槽旧值,再覆盖该槽”。未满时旧槽为初始零,减去它不影响真实总和;满了以后减去的正好是最老元素。读取旧值必须在覆盖之前完成,否则会把新值误当作需要淘汰的值。
另用
count保存当前有效数量,每收到一个数就增加,但达到容量后不再增加。数组的物理长度始终为size,实际分母却应为count,这样未填满阶段的平均值也正确。最后先把总和转成浮点数,再除以有效数量。窗口和使用宽整数保存,避免先用较窄整数完成累计或差值计算;这一转换与环形数组位置管理分别解决数值与数据保留的问题。
解题步骤
- 构造时创建长度为
size的全零数组,将写入位置、有效数量和总和都初始化为零。- 收到新值后,先从总和扣除
values[head],再加上新值。- 把新值写入该槽,将写入位置循环推进一格。
- 窗口未满时增加
count,满了以后保持容量大小。- 返回浮点总和除以
count的结果。
代码实现
class MovingAverage {
private final int[] values;
private int head;
private int count;
private long sum;
public MovingAverage(int size) {
values = new int[size];
}
public double next(int val) {
sum += (long) val - values[head];
values[head] = val;
head = (head + 1) % values.length;
if (count < values.length) {
count++;
}
return (double) sum / count;
}
}
type MovingAverage struct {
values []int
head, count int
sum int64
}
func Constructor(size int) MovingAverage { return MovingAverage{values: make([]int, size)} }
func (m *MovingAverage) Next(val int) float64 {
m.sum += int64(val) - int64(m.values[m.head])
m.values[m.head] = val
m.head = (m.head + 1) % len(m.values)
if m.count < len(m.values) {
m.count++
}
return float64(m.sum) / float64(m.count)
}
复杂度分析
- 时间复杂度:构造固定数组为
O(size);每次next只读写一个槽位并更新几个变量,为O(1)。- 空间复杂度:
O(size)。只保留最近窗口需要的固定数量槽位,不随数据流总长度增长。
关键点总结
[!green]
- 环形数组按先入先出次序覆盖,写入指针定位当前要淘汰的最老数据。
- 滚动和只增新值、减旧值,不必重新累加整个窗口。
head决定写入位置,count决定有效分母,两者含义不同。- 先读取旧值再覆盖,先转浮点再除法。
易错点总结
[!yellow]
- 窗口未满时直接除以容量:会把不存在的数据当成零加入平均,应除以实际数量。
- 覆盖槽位后才扣除旧值:旧值已经丢失,滚动和会计算错误。
- 忘记扣掉被覆盖的数:总和会变成整个历史数据流之和,而不是当前窗口和。
- 写入下标不回绕:达到容量后会越界,需要循环推进。
- 整数相除后再转浮点:小数部分已经丢失,类型转换应先于除法。
- 让
count无限增加:窗口最多保留容量个数,满了以后分母必须固定。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 933. 最近的请求次数 | 简单 | 同样移除不再属于窗口的旧数据,但窗口由时间戳界定,而非固定元素数量。 |
| 622. 设计循环队列 | 中等 | 复用环形数组下标回绕;本题额外维护窗口和,并允许新值覆盖最老值。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!