Snapshot Array

MediumDesign · Binary Search🧩 Data Structure DesignLeetCode #1146 ↗Updated

Problem

Design a SnapshotArray(length) whose cells all start at 0. set(index, val) writes a cell, snap() freezes the current contents and returns a snapshot id (0, 1, 2, …), and get(index, snapId) returns what that cell held when snapshot snapId was taken.
InputSnapshotArray(3); set(0,5); snap(); set(0,6); get(0,0)Outputsnap() = 0, get = 5
InputSnapshotArray(2); snap(); snap(); set(1,4); snap(); get(1,1); get(1,2)Output0, 1, 2, then 0, 4

1 ≤ length ≤ 5·104  ·  0 ≤ val ≤ 109  ·  snapId < number of snap() calls  ·  ≤ 5·104 calls in total

Try it yourself

Java
class SnapshotArray {
    public SnapshotArray(int length) {
        
    }

    public void set(int index, int val) {
        
    }

    public int snap() {
        
    }

    public int get(int index, int snap_id) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Don’t copy the array per snapshot — record only changes. Each cell keeps a history of (snapId, value) pairs in increasing snapId order; get is then “last change with snapId ≤ asked id”, a binary search.
PatternData Structure Design

Instinct Implement a class whose methods must all hit a time bound (often O(1)) - write each method's target cost, then glue a hash map to a structure that keeps order.

Memory hack Finder plus keeper - a hash map finds it fast, a list/heap/array keeps the order, and every method updates both.

Open the Data Structure Design guide →

Approaches

Brute ForceFull Copy per Snapshot

Time snap O(length), set/get O(1)Space O(length · snaps)

Keep the live array and, on every snap(), store a complete copy of it. Reading an old snapshot is then a plain array access.

Java
class SnapshotArray {
    private final int[] current;
    private final List<int[]> snaps = new ArrayList<>();
    public SnapshotArray(int length) { current = new int[length]; }
    public void set(int index, int val) { current[index] = val; }
    public int snap() {
        snaps.add(current.clone()); // freeze a full copy
        return snaps.size() - 1;
    }
    public int get(int index, int snap_id) { return snaps.get(snap_id)[index]; }
}

Fast reads, but 5·104 snaps of a 5·104-cell array is billions of ints.

At a glance

LevelApproachTimeSpace
Brute ForceFull Copy per Snapshotsnap O(length), set/get O(1)O(length · snaps)
BetterPer-Snapshot Change Maps + Backward Scanset/snap O(1), get O(snaps)O(sets)
OptimalPer-Cell History + Binary Searchset/snap O(1), get O(log h)O(length + sets)
Common pitfalls
  • Copying the whole array on snap() — blows memory with many snapshots.
  • Appending a new pair for every write in the same snapshot — history grows without need.
  • Off-by-one in ids: snap() returns the id before incrementing.
Interview tips
  • Start from the full-copy idea, then ask “what actually changes between snaps?”
  • Point out the history is sorted for free because snapshot ids only grow.
  • Compare per-snapshot vs. per-cell storage: the second makes reads logarithmic.
Pattern
Versioned storage + floor search → reading old states of data that changes over time.
My notes saved in this browser