LeetCode 1622. 奇妙序列

LeetCode 1622. 奇妙序列

题目描述

实现一个 Fancy 类,支持以下操作:

  • Fancy():初始化对象。
  • void append(int val):将整数 val 添加到序列末尾。
  • void addAll(int inc):将序列中的所有现有值增加 inc
  • void multAll(int m):将序列中的所有现有值乘以 m
  • int getIndex(int idx):获取当前序列中下标为 idx 的值(从 0 开始),如果下标越界则返回 -1

所有运算结果需要对 10^9 + 7 取模。

思路分析

如果每次操作都直接更新数组中的所有元素,时间复杂度会很高。我们需要一种**延迟更新(lazy propagation)**的技巧,将全局的加法和乘法因子记录下来,在查询时才真正计算元素的值。

核心思想是维护两个全局变量:

  • mul:全局乘法因子(初始为 1)
  • add:全局加法偏移(初始为 0)

对于序列中的每个元素,我们存储一个基值 base,使得该元素的实际值等于:

复制代码
实际值 = (base * mul + add) % MOD

这样,每次全局更新时只需要修改 muladd,而无需改动每个 base。当添加新元素时,我们需要根据当前的 (mul, add) 计算出适当的 base,使得应用当前变换后能得到待添加的值。

为什么能这样做?

我们可以把对序列的操作看作是对每个元素依次应用一个线性变换 f(x)=mul⋅x+addf(x) = mul \cdot x + addf(x)=mul⋅x+add。由于所有元素共享同一组变换参数,所以当我们要追加一个值 val 时,我们需要找到一个 base 使得 f(base)=valf(base) = valf(base)=val,即:
base=val−addmul(modMOD) base = \frac{val - add}{mul} \pmod{MOD} base=mulval−add(modMOD)

其中除法用乘法逆元实现。这样存储的 base 就与当前的变换绑定,后续的全局变换只会通过 muladd 影响最终结果,而 base 保持不变。

数据结构设计

  • vector<int> vals:存储每个元素添加时计算的基值 base
  • long long mul:全局乘法因子,初始为 1。
  • long long add:全局加法偏移,初始为 0。
  • 模数 MOD = 1e9 + 7

辅助函数:快速幂求逆元

由于 MOD 是质数,我们可以用费马小定理求逆元:a^(MOD-2) % MOD 即为 a 的模逆元。快速幂实现如下:

cpp 复制代码
long long pow(long long x, int n) {
    long long res = 1;
    for (; n; n /= 2) {
        if (n % 2) res = res * x % MOD;
        x = x * x % MOD;
    }
    return res;
}

代码实现(C++)

cpp 复制代码
class Fancy {
    static constexpr int MOD = 1e9 + 7;

    vector<int> vals;      // 存储每个元素的基值
    long long add = 0;      // 全局加法偏移
    long long mul = 1;      // 全局乘法因子

    // 快速幂:计算 x^n mod MOD
    long long pow(long long x, int n) {
        long long res = 1;
        for (; n; n /= 2) {
            if (n % 2) res = res * x % MOD;
            x = x * x % MOD;
        }
        return res;
    }

public:
    Fancy() {}

    void append(int val) {
        // 计算基值 base = (val - add) / mul  (模意义下)
        // 逆元用快速幂求
        long long base = (val - add + MOD) * pow(mul, MOD - 2) % MOD;
        vals.push_back(base);
    }

    void addAll(int inc) {
        add = (add + inc) % MOD;
    }

    void multAll(int m) {
        mul = mul * m % MOD;
        add = add * m % MOD;
    }

    int getIndex(int idx) {
        if (idx >= vals.size()) return -1;
        // 实际值 = vals[idx] * mul + add
        return (vals[idx] * mul + add) % MOD;
    }
};

正确性证明(归纳法)

我们用数学归纳法证明:经过任意次操作后,对于任意索引 i,其实际值始终等于 (vals[i] * mul + add) % MOD

  • 初始状态mul=1, add=0vals 为空,命题平凡成立。
  • 添加元素 :执行 append(val) 时,我们构造的 base 满足 (base * mul + add) % MOD = val,因此新元素符合命题。已有元素未变,命题保持。
  • 全局加法 :执行 addAll(inc) 后,add' = add + inc。对于任意元素,原值 x = (vals[i] * mul + add) % MOD,新值 x' = x + inc = vals[i] * mul + (add + inc) = vals[i] * mul + add',符合新变换。
  • 全局乘法 :执行 multAll(m) 后,mul' = mul * madd' = add * m。原值 x = vals[i] * mul + add,新值 x' = m * x = vals[i] * (mul * m) + (add * m) = vals[i] * mul' + add',符合新变换。

因此,getIndex 总能通过当前 (mul, add) 和存储的 vals[i] 计算出正确的实际值。

复杂度分析

  • 时间复杂度:每个操作(append, addAll, multAll, getIndex)均为 O(1)(快速幂求逆元为 O(log MOD),可视为常数)。
  • 空间复杂度:O(n),其中 n 为调用 append 的次数。

注意事项

  • mul = 0 时,逆元不存在,上述代码中的 pow(mul, MOD-2) 会出错。在本题的测试数据中,通常不会出现需要在 mul = 0 时执行 append 的情况。如果必须处理,可以在 append 前检查:若 mul == 0,则先将所有现有元素的实际值 计算出来存入新数组,并重置 mul = 1, add = 0,然后再添加新元素。但本题代码未做此处理,利用题目隐含条件即可通过。
  • 注意取模运算中可能出现负数,需要加 MOD 调整。

总结

本题巧妙地将对序列的整体操作转化为两个全局参数的维护,并利用模逆元在添加元素时存储"逆像",从而避免了每次操作都修改全体元素。这种延迟更新思想在区间操作类问题中非常常见,值得掌握。

相关推荐
6Hzlia4 分钟前
【Hot 100 刷题计划】 LeetCode 148. 排序链表 | C++ 归并排序自顶向下
c++·leetcode·链表
自我意识的多元宇宙8 分钟前
数据结构----插入排序
数据结构·算法·排序算法
im_AMBER8 分钟前
Leetcode 162 除了自身以外数组的乘积 | 接雨水
开发语言·javascript·数据结构·算法·leetcode
Westward-sun.9 分钟前
YOLO目标检测算法与mAP评估指标详解(附示例)
算法·yolo·目标检测
cpp_250119 分钟前
P1873 [COCI 2011/2012 #5] EKO / 砍树
数据结构·c++·算法·题解·二分答案·洛谷·csp
啊哦呃咦唔鱼21 分钟前
leetcodehot100-347. 前 K 个高频元素
数据结构·算法·leetcode
玛丽莲茼蒿21 分钟前
Leetcode hot100 多数元素【简单】
算法·leetcode·职场和发展
AbandonForce22 分钟前
Map类:pair键值对|map的基本操作|operator[]
开发语言·c++·算法·leetcode
澈20724 分钟前
C++核心:封装与static静态成员实战指南
开发语言·c++·算法
田梓燊28 分钟前
力扣:146.LRU 缓存
算法·leetcode·缓存