1146. Snapshot Array - Detailed Explanation

Free Coding Questions Catalog
Boost your coding skills with our essential coding questions catalog. Take a step towards a better tech career now!

Problem Statement

Implement a data structure called SnapshotArray that supports the following operations:

  • SnapshotArray(int length): Initializes an array-like data structure of the given length with all elements initialized to 0.

  • void set(int index, int val): Sets the element at the specified index to be equal to val.

  • int snap(): Takes a snapshot of the array and returns the snapshot id (the number of times snap() has been called minus one).

  • int get(int index, int snap_id): Returns the value at the specified index at the time the snapshot with id snap_id was taken.

Example 1

  • Input:
    • Commands: ["SnapshotArray", "set", "snap", "set", "get"]
    • Arguments: [[3], [0,5], [], [0,6], [0,0]]
  • Output: [null, null, 0, null, 5]
  • Explanation:
    1. Initialize a SnapshotArray of length 3: [0, 0, 0].

    2. Call set(0, 5): Array becomes [5, 0, 0].

    3. Call snap(): Takes a snapshot and returns snap id 0.

    4. Call set(0, 6): Array becomes [6, 0, 0].

    5. Call get(0, 0): Returns the value at index 0 from snapshot 0, which is 5.

Example 2

  • Input:
    • Commands: ["SnapshotArray", "set", "set", "snap", "set", "get"]
    • Arguments: [[1], [0,15], [0,20], [], [0,25], [0,0]]
  • Output: [null, null, null, 0, null, 20]
  • Explanation:
    1. Initialize a SnapshotArray of length 1: [0].

    2. Call set(0, 15): Array becomes [15].

    3. Call set(0, 20): Array becomes [20].

    4. Call snap(): Takes a snapshot and returns snap id 0.

    5. Call set(0, 25): Array becomes [25].

    6. Call get(0, 0): Returns the value at index 0 from snapshot 0, which is 20.

Example 3

  • Input:
    • Commands: ["SnapshotArray", "snap", "get", "set", "snap", "get"]
    • Arguments: [[2], [], [1,0], [1,5], [], [1,1]]
  • Output: [null, 0, 0, null, 1, 5]
  • Explanation:
    1. Initialize a SnapshotArray of length 2: [0, 0].

    2. Call snap(): Takes a snapshot and returns snap id 0.

    3. Call get(1, 0): Returns the value at index 1 from snapshot 0, which is 0.

    4. Call set(1, 5): Array becomes [0, 5].

    5. Call snap(): Takes another snapshot and returns snap id 1.

    6. Call get(1, 1): Returns the value at index 1 from snapshot 1, which is 5.

Constraints

  • 1 <= length <= 50,000
  • 0 <= index < length
  • 0 <= val <= 10⁹
  • At most 50,000 calls will be made to set, snap, and get.

Hints to Approach the Problem

  • Hint 1:
    Think about how you might avoid copying the entire array every time a snapshot is taken. Is there a way to record only the changes (updates) instead of saving the whole state?

  • Hint 2:
    For each index, you could maintain a history of changes (each with the snapshot id and value). Then, when you call get(index, snap_id), you only need to find the most recent update that occurred on or before that snapshot id. How might you efficiently search through these changes?

A. Brute Force Approach

Explanation

  • Idea:
    Every time a snap() is called, copy the entire array and store it. For a get(index, snap_id), simply look up the value in the stored snapshot.

  • Pros:
    Easy to understand and implement.

  • Cons:

    • Memory inefficient: If there are many snapshots and a large array, storing each full snapshot leads to high space usage.
    • Time inefficient: Taking a snapshot is O(n) where n is the length of the array.

Visual Example

Imagine an array of length 3:

  • Start: [0, 0, 0]
  • After set(0, 5): [5, 0, 0]
  • Snapshot taken → copy [5, 0, 0]
  • After set(0, 6): [6, 0, 0]
  • To answer get(0, snap_id=0), retrieve the value from the stored snapshot: 5.

Explanation

  • Idea:
    Instead of copying the entire array, store for each index only the changes.
    For each index, maintain a list of (snap_id, value) pairs.

    • set(index, val): Update the current snapshot’s value for the given index.
    • snap(): Increment the current snapshot id and return the previous id.
    • get(index, snap_id): Use binary search to quickly find the latest change for the index that is less than or equal to the given snap_id.
  • Advantages:

    • Memory efficient: Only changes are stored.
    • Time efficient: get is O(log m) per call (where m is the number of changes at that index).

Visual Example

Consider an index that initially has a history: [(0, 0)] (value 0 at snapshot 0).

  • After set(index, 5) before any snapshot, update to [(0, 0), (current_snap, 5)].

  • When snap() is called, the current snapshot id (say 0) is fixed.

  • If later set(index, 6) is called in the new snapshot, append a new record.

  • To get the value for a previous snapshot, binary search finds the most recent pair with a snap_id less than or equal to the query.

Python Implementation

Python3
Python3

. . . .

Java Implementation

Java
Java

. . . .

Complexity Analysis

Brute Force Approach

  • Time Complexity:
    • set: O(1)
    • snap: O(n) (due to copying the entire array)
    • get: O(1)
  • Space Complexity: O(n * s) where s is the number of snapshots.

Optimal Approach (History with Binary Search)

  • Time Complexity:
    • set: O(1) amortized (updating or appending to the list)
    • snap: O(1) (simply increments the snapshot id)
    • get: O(log m) (binary search on the list of updates at a given index, where m is the number of changes for that index)
  • Space Complexity: O(n + total number of set calls)
    Only changes are stored rather than the entire array for every snapshot.

Step-by-Step Walkthrough (Using the Optimal Approach)

  1. Initialization:

    • For an array of length 3, initialize a list for each index:
      • Index 0: [(0, 0)]
      • Index 1: [(0, 0)]
      • Index 2: [(0, 0)]
    • current_snap is set to 0.
  2. set(0, 5):

    • Update index 0. Since the last update for index 0 is from snapshot 0, append or update:
      • Index 0 becomes: [(0, 5)]
  3. snap():

    • Increment current_snap to 1.
    • Return snap_id 0.
  4. set(0, 6):

    • In the new snapshot (current_snap = 1), update index 0:
      • Index 0 list becomes: [(0, 5), (1, 6)]
  5. get(0, 0):

    • Perform a binary search on the list for index 0:
      • Find the largest snap_id less than or equal to 0.
      • The pair (0, 5) is returned → value is 5.

Common Mistakes

  • Copying the Entire Array:
    Many solutions mistakenly copy the entire array during each snapshot, leading to high time and space complexity.
  • Not Using Binary Search in get:
    Scanning the entire list of changes for an index can lead to timeouts on large inputs.
  • Overwriting Instead of Appending:
    Forgetting to handle multiple set() operations in the same snapshot might lead to incorrect data retrieval.

Edge Cases

  • No Changes Before Snapshot:
    If no set() call is made before a snapshot, the default value (0) should be returned.

  • Multiple Sets on the Same Index:
    Ensure that if multiple set() operations occur in the same snapshot, only the latest one is stored.

  • Large Number of Snapshots:
    The solution should handle many snapshots without copying the entire array each time.

  • Variations:
    • Implementing a persistent data structure where previous versions are accessible.
    • Extending the idea to support range queries with snapshots.
  • Related Problems for Further Practice:
    • Time Based Key-Value Store:
      Implement a key-value store that can return the value at a specific timestamp.

    • Range Sum Query Immutable:
      Preprocess an array for quick range sum queries.

    • Design Hit Counter:
      Create a data structure to count hits over a sliding time window.

    • Range Sum Query 2D - Immutable:
      Extend range queries to a 2D grid.

  • Time Based Key-Value Store
  • Design Hit Counter
  • Range Sum Query - Immutable
  • Range Sum Query 2D - Immutable
TAGS
leetcode
CONTRIBUTOR
Design Gurus Team
-

GET YOUR FREE

Coding Questions Catalog

Design Gurus Newsletter - Latest from our Blog
Boost your coding skills with our essential coding questions catalog.
Take a step towards a better tech career now!
Explore Answers
Related Courses
Grokking the Coding Interview: Patterns for Coding Questions
Grokking the Coding Interview Patterns in Java, Python, JS, C++, C#, and Go. The most comprehensive course with 476 Lessons.
Grokking Modern AI Fundamentals
Master the fundamentals of AI today to lead the tech revolution of tomorrow.
Grokking Data Structures & Algorithms for Coding Interviews
Unlock Coding Interview Success: Dive Deep into Data Structures and Algorithms.
Image
One-Stop Portal For Tech Interviews.
Copyright © 2025 Design Gurus, LLC. All rights reserved.
;