1. 问题背景与题目解析
LeetCode 1622题"奇妙序列"是一道考察数据结构设计与算法优化的经典题目。题目要求我们设计一个序列数据结构,支持三种操作:
add(x):将整数x添加到序列末尾multAll(m):将序列中所有元素乘以整数mgetIndex(i):返回序列中第i个元素的当前值(下标从0开始)
这道题的难点在于,当序列长度很大(比如1e5)且操作次数很多(比如1e5)时,如何高效处理这些操作。直接按照题意暴力实现会导致O(n)时间复杂度的multAll操作,这在数据量大时显然不可行。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 懒更新(Lazy Propagation)的核心思想
懒更新是一种延迟计算的技术,广泛应用于线段树等数据结构中。其核心思想是:当需要对一个区间进行批量操作时,不立即执行所有更新,而是记录下这个操作,等到真正需要查询具体值时再应用这些更新。
在本题中,我们可以将乘法操作视为一种"懒标记"。具体来说:
- 维护两个变量:
base表示基础乘数,offset表示当前未应用的乘法操作 - 当执行
multAll(m)时,不立即遍历所有元素,而是将offset *= m - 当执行
getIndex(i)时,才将offset应用到该元素上
这种技术将O(n)的即时更新转化为O(1)的延迟标记和O(1)的按需计算,大幅提升了效率。
3. 具体实现方案
3.1 数据结构设计
我们需要维护以下数据结构:
- 一个列表
seq存储原始添加的值 - 一个变量
mul记录当前的乘法累积 - 一个列表
add_mul记录每个元素添加时的mul值
python复制class Fancy:
def __init__(self):
self.seq = []
self.mul = 1
self.add_mul = []
3.2 操作实现细节
add操作:
python复制def add(self, x: int) -> None:
self.seq.append(x)
self.add_mul.append(self.mul)
multAll操作:
python复制def multAll(self, m: int) -> None:
self.mul = (self.mul * m) % (10**9 + 7)
getIndex操作:
python复制def getIndex(self, i: int) -> int:
if i >= len(self.seq):
return -1
# 计算逆元并应用延迟更新
inv = pow(self.add_mul[i], 10**9 + 5, 10**9 + 7)
val = self.seq[i] * self.mul % (10**9 + 7)
return val * inv % (10**9 + 7)
3.3 数学原理与模运算
这里用到了费马小定理来计算乘法逆元。因为题目要求结果对1e9+7取模,而1e9+7是质数,所以可以用:
code复制a^(-1) ≡ a^(p-2) mod p
来计算a的逆元,其中p=1e9+7。
4. 复杂度分析与优化
4.1 时间复杂度
add: O(1)multAll: O(1)getIndex: O(log MOD) (因为需要计算逆元)
4.2 空间复杂度
O(n),需要存储原始序列和每个元素添加时的乘法标记。
4.3 进一步优化思路
可以预计算一些常用逆元来加速getIndex操作,或者使用更高效的逆元计算方法。
5. 常见错误与调试技巧
5.1 乘法溢出问题
即使使用Python(没有整数溢出),也应该注意模运算的时机。错误的模运算顺序可能导致结果不正确。
5.2 边界条件处理
特别注意:
- 空序列时的
getIndex调用 - 非常大的索引值
- 连续的
multAll操作
5.3 测试用例设计
建议测试以下场景:
- 交替进行add和multAll操作
- 连续多次multAll后查询
- 大规模数据下的性能测试
6. 同类问题扩展
懒更新技术还适用于以下LeetCode题目:
- 307.区域和检索 - 数组可修改(线段树懒更新)
- 699.掉落的方块(区间更新)
- 715.Range模块(区间管理)
掌握这种思想可以帮助解决许多区间操作问题。
7. 实际工程中的应用
虽然这是一道算法题,但懒更新思想在工程中也有广泛应用:
- 数据库中的批量更新优化
- 游戏开发中的物理引擎计算
- 图形渲染中的批量绘制调用
理解这种延迟计算的理念对设计高性能系统很有帮助。
8. 个人实现中的经验分享
在实际编码时,我发现了几个值得注意的点:
-
逆元计算的时机:最初我尝试在add时就计算逆元,但这会导致后续multAll操作无法正确应用。正确的做法是在getIndex时才计算。
-
模运算的陷阱:Python的pow函数第三个参数是模数,但要注意它和普通乘法的结合顺序。我最初写成了
(val * inv) % MOD,但实际上应该先对每个因子取模。 -
测试用例的重要性:这道题的边界条件很容易忽略,特别是当序列为空或索引越界时。建议先写测试用例再实现逻辑。
-
性能优化:对于Python实现,使用内置的pow函数比手写快速幂要快很多。在类似问题中,尽量使用语言提供的高效数学函数。
