Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1622. Fancy Sequence - Leetcode Solution

Problem Description

The Fancy Sequence problem asks you to implement a special data structure that supports the following operations efficiently:

  • append(val): Add an integer val to the end of the sequence.
  • addAll(inc): Increment every element in the sequence by inc.
  • multAll(m): Multiply every element in the sequence by m.
  • getIndex(idx): Get the current value at index idx of the sequence, or -1 if idx is out of bounds.

All operations must be performed modulo 10^9 + 7 (to avoid overflow).

Key constraints:

  • All operations must be efficient (not O(N) per operation).
  • The sequence can be long (up to 105 elements).
  • Each getIndex must return the value as if all previous operations have been applied.

Thought Process

At first glance, it seems we could just keep an array of numbers and, for each addAll or multAll, iterate through and update every element. But this would be too slow for large sequences, especially if we have many such operations.

The challenge is to avoid directly updating every element for each operation, and instead come up with a way to "simulate" these operations efficiently.

The key insight is to use lazy propagation: we can keep track of the total multiplication and addition applied to the whole sequence so far, and for each element, store what the state was when it was appended. Then, when we need the value at a certain index, we can "replay" the difference in operations.

This is similar to how segment trees or lazy propagation work, but at a per-element granularity.

Solution Approach

Let's break down the approach step by step:

  1. Track cumulative operations:
    • Maintain two variables: mul (current total multiplication) and add (current total addition).
    • Both start at 1 and 0 respectively.
  2. Appending elements:
    • When we append a new value, we store not just the value, but also the state of mul and add at that moment.
  3. Applying addAll and multAll:
    • For addAll(inc), just update add = (add + inc) % MOD.
    • For multAll(m), update both: mul = (mul * m) % MOD, add = (add * m) % MOD.
  4. Getting an element:
    • Suppose an element was appended when mul = m0 and add = a0.
    • Now, mul = m1 and add = a1.
    • We want to apply the difference: the net effect is x * (m1/m0) + (a1 - a0 * (m1/m0)).
    • Because division is tricky with mod, we use modular inverse to compute m1 * inv(m0) % MOD.
  5. Modular inverse:
    • Since MOD is prime, we can use Fermat's Little Theorem for modular inverse.

This way, we never need to update all elements explicitly; all operations are O(1).

Example Walkthrough

Let's walk through a sample sequence:

  1. append(2) (mul=1, add=0): store (2, 1, 0)
  2. addAll(3) (mul=1, add=3)
  3. append(7) (mul=1, add=3): store (7, 1, 3)
  4. multAll(2) (mul=2, add=6)
  5. getIndex(0):
    • Original val=2, appended at mul=1, add=0
    • Current mul=2, add=6
    • Factor = 2/1 = 2 (modular inverse)
    • Result = 2 * 2 + (6 - 0*2) = 4 + 6 = 10
  6. getIndex(1):
    • Original val=7, appended at mul=1, add=3
    • Current mul=2, add=6
    • Factor = 2/1 = 2
    • Result = 7 * 2 + (6 - 3*2) = 14 + (6 - 6) = 14

Time and Space Complexity

Brute-force approach:

  • addAll and multAll would take O(N) per operation, leading to O(Q*N) total time for Q operations.
Optimized approach:
  • All operations (append, addAll, multAll, getIndex) are O(1) due to lazy propagation and modular arithmetic.
  • Space is O(N) for storing the sequence and per-element state.

This efficiency is crucial for handling large sequences and many operations.

Summary

The Fancy Sequence problem teaches us how to efficiently handle global updates on a dynamic array using lazy evaluation and modular arithmetic. By storing the state of operations at each append, and replaying only the necessary difference with modular inverses, we avoid costly updates and achieve O(1) time for all operations. This approach is both elegant and practical, leveraging mathematical properties for efficient computation.

Code Implementation

MOD = 10 ** 9 + 7

def modinv(x):
    return pow(x, MOD - 2, MOD)

class Fancy:

    def __init__(self):
        self.seq = []
        self.mul = 1
        self.add = 0

    def append(self, val: int) -> None:
        # Store value and the state of mul/add at append time
        self.seq.append((val, self.mul, self.add))

    def addAll(self, inc: int) -> None:
        self.add = (self.add + inc) % MOD

    def multAll(self, m: int) -> None:
        self.mul = (self.mul * m) % MOD
        self.add = (self.add * m) % MOD

    def getIndex(self, idx: int) -> int:
        if idx >= len(self.seq):
            return -1
        val, mul0, add0 = self.seq[idx]
        # Compute factor = self.mul / mul0 mod MOD
        factor = self.mul * modinv(mul0) % MOD
        res = (val * factor) % MOD
        res = (res + (self.add - add0 * factor) % MOD) % MOD
        return res
      
class Fancy {
    static const int MOD = 1e9 + 7;
    vector<tuple<int, int, int>> seq;
    long long mul = 1, add = 0;
    
    long long modinv(long long x) {
        long long res = 1, p = MOD - 2;
        while (p) {
            if (p & 1) res = res * x % MOD;
            x = x * x % MOD;
            p >>= 1;
        }
        return res;
    }
public:
    Fancy() {}

    void append(int val) {
        seq.push_back({val, mul, add});
    }
    
    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 >= seq.size()) return -1;
        long long val, mul0, add0;
        tie(val, mul0, add0) = seq[idx];
        long long factor = mul * modinv(mul0) % MOD;
        long long res = (val * factor) % MOD;
        res = (res + (add - add0 * factor % MOD + MOD) % MOD) % MOD;
        return (int)res;
    }
};
      
class Fancy {
    static final int MOD = 1000000007;
    List<long[]> seq;
    long mul, add;

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

    private long modinv(long x) {
        long res = 1, p = MOD - 2;
        while (p > 0) {
            if ((p & 1) != 0) res = res * x % MOD;
            x = x * x % MOD;
            p >>= 1;
        }
        return res;
    }

    public void append(int val) {
        seq.add(new long[]{val, mul, add});
    }
    
    public void addAll(int inc) {
        add = (add + inc) % MOD;
    }
    
    public void multAll(int m) {
        mul = (mul * m) % MOD;
        add = (add * m) % MOD;
    }
    
    public int getIndex(int idx) {
        if (idx >= seq.size()) return -1;
        long[] data = seq.get(idx);
        long val = data[0], mul0 = data[1], add0 = data[2];
        long factor = mul * modinv(mul0) % MOD;
        long res = (val * factor) % MOD;
        res = (res + (add - add0 * factor % MOD + MOD) % MOD) % MOD;
        return (int)res;
    }
}
      
var MOD = 1e9 + 7;

function modinv(x) {
    let res = 1, p = MOD - 2;
    x = x % MOD;
    while (p > 0) {
        if (p & 1) res = res * x % MOD;
        x = x * x % MOD;
        p = Math.floor(p / 2);
    }
    return res;
}

var Fancy = function() {
    this.seq = [];
    this.mul = 1;
    this.add = 0;
};

Fancy.prototype.append = function(val) {
    this.seq.push([val, this.mul, this.add]);
};

Fancy.prototype.addAll = function(inc) {
    this.add = (this.add + inc) % MOD;
};

Fancy.prototype.multAll = function(m) {
    this.mul = (this.mul * m) % MOD;
    this.add = (this.add * m) % MOD;
};

Fancy.prototype.getIndex = function(idx) {
    if (idx >= this.seq.length) return -1;
    let [val, mul0, add0] = this.seq[idx];
    let factor = this.mul * modinv(mul0) % MOD;
    let res = (val * factor) % MOD;
    res = (res + (this.add - add0 * factor % MOD + MOD) % MOD) % MOD;
    return res;
};