leetcode
leetcode 1451 ~ 1500
奇妙序列

奇妙序列

难度:

标签:

题目描述

代码结果

运行时间: 436 ms, 内存: 53.9 MB


/* 
 * 题目思路:
 * 与Java代码相同,只是这里使用Java Stream处理append操作。 
 * 我们还是延迟更新策略,在getIndex时计算实际值。
 */
import java.util.ArrayList;
import java.util.stream.IntStream;

public class Fancy {
    private static final int MOD = 1_000_000_007;
    private ArrayList<Integer> sequence;
    private int add;
    private int mul;

    public Fancy() {
        sequence = new ArrayList<>();
        add = 0;
        mul = 1;
    }

    public void append(int val) {
        val = (int)((val - add + MOD) % MOD);
        val = (int)((val * modInverse(mul, MOD)) % MOD);
        sequence.add(val);
    }

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

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

    public int getIndex(int idx) {
        if (idx >= sequence.size()) {
            return -1;
        }
        return (int)(((long)sequence.get(idx) * mul + add) % MOD);
    }

    private int modInverse(int a, int m) {
        return power(a, m - 2, m);
    }

    private int power(int x, int y, int p) {
        int res = 1;
        x = x % p;
        while (y > 0) {
            if ((y & 1) == 1)
                res = (res * x) % p;
            y = y >> 1;
            x = (x * x) % p;
        }
        return res;
    }
}

解释

方法:

这个题解利用线性变换的组合来避免对序列每个元素逐一进行操作,从而提高效率。每次添加或乘法操作时,我们不直接更新序列,而是记录当前操作的一次项系数和常数项,并将其与之前的操作合并。当调用getIndex时,我们使用保存的操作状态和反函数来计算具体位置的数值。这种方法把对整个序列的操作压缩为对两个参数(一次项系数和常数项)的操作,大大降低了处理时间。

时间复杂度:

O(1) per operation

空间复杂度:

O(n)

代码细节讲解

🦆
请问在`Fancy`类中,`k`和`b`数组分别用来存储什么信息?它们是如何反映序列操作的历史的?
`k`和`b`数组在`Fancy`类中用于存储每次操作后序列的线性变换参数。具体来说,`k`数组存储的是每次操作后的一次项系数,而`b`数组存储的是常数项。这两个数组的每个元素对应一次操作的状态,反映了从初始状态到当前状态的所有线性变换的累积效果。每当进行添加或乘法操作时,`k`和`b`都会添加新的元素,这些元素基于前一次的值进行计算,从而记录了操作的历史并使得之后的任何时间点都能追溯到任何先前的状态。
🦆
为什么在`addAll`和`multAll`方法中更新`k`和`b`时需要取模运算?取模运算的目的是什么?
在`addAll`和`multAll`方法中更新`k`和`b`时需要取模运算主要是为了防止数值溢出,并确保所有计算都在一个可控的数值范围内进行。这里的模数`MOD`为`10**9 + 7`,一个常用的大质数,可以有效减少由于大数运算带来的性能问题和精度问题。取模运算也有利于保持结果的一致性和可预测性,特别是在面对大量操作和大数据量的情况下。
🦆
在`getIndex`方法中,为什么需要计算`one_over_k`并使用费马小定理?这里的费马小定理是如何应用的?
在`getIndex`方法中,需要计算`one_over_k`作为当前一次项系数的逆元,以便能够将累积的线性变换逆向应用到具体的序列值上。费马小定理指出,如果`p`是一个质数且`a`是一个不被`p`整除的整数,则`a^(p-1) ≡ 1 (mod p)`。由此可得`a^(p-2) ≡ a^(-1) (mod p)`。在这里,`MOD`是质数,所以可以使用费马小定理来计算`k`的逆元`one_over_k`,即`one_over_k = pow(k, MOD-2, MOD)`。这个逆元使得我们可以解除之前所有乘法操作的影响,从而正确地计算出原始值。
🦆
在`getIndex`方法中,如果`idx`指向的元素在多次操作后,如何确保最终结果的正确性?
在`getIndex`方法中,为了确保最终结果的正确性,方法首先找到元素的初始插入时对应的操作版本索引`func_index`。这个索引帮助我们定位到元素被插入时的`k`和`b`的值。然后通过计算从那个时间点到当前的所有操作的累积效应,使用当前的`k`和`b`值以及之前保存的那些值,通过反函数来取消之前的变换,最终得到正确的元素值。这种方法确保了无论多少次操作,每次请求的复杂度都被限制在常数时间内,同时保证了操作的正确性。

相关问题