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
For example:
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 * 10^4
0 <= index < length
0 <= val <= 10^9
0 <= snap_id <
(the total number of times we call snap()
)`5 * 10^4
calls will be made to set
, snap
, and get
.This problem requires implementing a data structure that simulates an array with snapshot capabilities. Let's explore a couple of approaches.
The most straightforward approach is to store a complete copy of the array for each snapshot. This makes the get
operation very fast but significantly increases memory consumption and the time complexity of the snap
operation.
Implementation Details:
SnapshotArray(int length)
: Initializes an array of the given length, filled with 0s.set(int index, int val)
: Sets the value at the given index in the current array.snap()
: Creates a copy of the current array and stores it in a list of snapshots. Returns the index of the new snapshot.get(int index, int snap_id)
: Retrieves the value at the given index from the snapshot with the given ID.Code Example (Python):
class SnapshotArray:
def __init__(self, length):
self.array = [0] * length
self.snapshots = {0: self.array[:]}
self.snap_id = 0
def set(self, index, val):
self.array[index] = val
def snap(self):
self.snap_id += 1
self.snapshots[self.snap_id] = self.array[:]
return self.snap_id - 1
def get(self, index, snap_id):
return self.snapshots[snap_id][index]
Time Complexity:
SnapshotArray()
: O(n), where n is the length of the array.set()
: O(1)snap()
: O(n), due to copying the entire array.get()
: O(1)Space Complexity:
A more efficient approach is to store the history of changes for each index. Instead of storing a full copy of the array for each snapshot, we only store the modifications made since the last snapshot. This dramatically reduces memory usage, especially when there are few modifications between snapshots.
Implementation Details:
history
. Each history[i]
will hold a list of (snap_id, value)
pairs, representing the value of the element at index i
at different snapshots.set(index, val)
is called, we append (current_snap_id, val)
to history[index]
. Note that current_snap_id
represents the next snap id, not the previous one.get(index, snap_id)
is called, we perform a binary search on history[index]
to find the largest snap_id
that is less than or equal to the given snap_id
. This gives the correct value at that snapshot.Code Example (Python):
class SnapshotArray:
def __init__(self, length):
self.history = [[] for _ in range(length)]
self.snap_id = 0
def set(self, index, val):
self.history[index].append((self.snap_id, val))
def snap(self):
self.snap_id += 1
return self.snap_id - 1
def get(self, index, snap_id):
history_list = self.history[index]
if not history_list:
return 0
# Binary search for the latest snap_id <= given snap_id
left, right = 0, len(history_list) - 1
ans = -1
while left <= right:
mid = (left + right) // 2
if history_list[mid][0] <= snap_id:
ans = mid
left = mid + 1
else:
right = mid - 1
if ans == -1:
return 0
else:
return history_list[ans][1]
Time Complexity:
SnapshotArray()
: O(n), where n is the length of the array.set()
: O(1)snap()
: O(1)get()
: O(log k), where k is the number of times the element at the given index has been modified.Space Complexity:
history[index]
is empty when get()
is called, it means the element at that index has never been set. In this case, we should return 0 (as per the problem description).snap_id
in history that's less than or equal to the provided snap_id
, it also means the value was never explicitly set at that snapshot, and the default value of 0 should be returned. Binary search takes care of this.