Facebook Pixel

Snapshot Array

Medium
LeetCode ↗

Implement a SnapshotArray that supports the following interface:

  • SnapshotArray(int length) initializes an array-like data structure with the given length. Initially, each element equals 0.
  • void set(index, val) sets the element at the given index to be equal to val.
  • int snap() takes a snapshot of the array and returns the snap_id: the total number of times we called snap() minus 1.
  • int get(index, snap_id) returns the value at the given index, at the time we took the snapshot with the given snap_id

Example 1:

Input: ["SnapshotArray","set","snap","set","get"]

[[3],[0,5],[],[0,6],[0,0]]

Output: [null,null,0,null,5]

Explanation:

SnapshotArray snapshotArr = new SnapshotArray(3); // set the length to be 3

snapshotArr.set(0,5);  // Set array[0] = 5

snapshotArr.snap();  // Take a snapshot, return snap_id = 0

snapshotArr.set(0,6);

snapshotArr.get(0,0);  // Get the value of array[0] with snap_id = 0, return 5

Constraints:

  • 1 <= length <= 5 * 104
  • 0 <= index < length
  • 0 <= val <= 109
  • 0 <= snap_id < (the total number of times we call snap())
  • At most 5 * 104 calls will be made to set, snap, and get.

Explanation

For each index i, histories[i] is a sorted list of (snap_id, value) pairs. To answer get(i, snap_id), we need the value written most recently at or before that snap_id. In other words, we need the rightmost pair whose snapshot id is <= snap_id.

This becomes a standard binary search boundary problem: if histories[i][mid][0] <= snap_id, then mid is a valid candidate, but there may be a later valid entry, so move right. Otherwise, move left. The final candidate index pos is the answer.

We initialize each history with (-1, 0), so pos is always valid and get correctly returns 0 when no set happened before that snapshot.

Implementation

class SnapshotArray(object):

def __init__(self, n):
    # set up histories so that each index has its own history
    self.histories = [[[-1, 0]] for _ in range(n)]
    self.snap_id = 0

def set(self, index, val):
    self.histories[index].append([self.snap_id, val])

def snap(self):
    self.snap_id += 1
    return self.snap_id - 1

def get(self, index, snap_id):
    left, right, pos = 0, len(self.histories[index])-1, -1
    while left <= right:
        mid = (left+right) // 2
        if self.histories[index][mid][0] <= snap_id:
            left = mid + 1
            pos = mid
        else:
            right = mid - 1
    return self.histories[index][pos][1]

Intuition

Instead of copying the entire array each time we take a snapshot, we wish to only store the changes to each index. we keep track of an array histories of size n where histories[i] is an array that stores the history of the changes of array[i]'s values. We use the pair (snap_id, value) to indicate that we have updated array[i]=value at the time we took the snapshot with the given snap_id.

So when implementing get(snap_id) for index i, we will do binary search on histories[i] to find the index pos in histories[i] that contains the most recent value up to the time we took the snapshot with the given snap_id.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More