🧠
Hash Map per Index with Binary Search (Optimal)
💡 This approach is the optimal solution combining efficient storage and fast queries by using binary search on snapshots per index.
Intuition
For each index, store a sorted list of (snap_id, val) pairs. On get, use binary search to find the largest snap_id ≤ requested snap_id to retrieve the value efficiently.
Algorithm
- Initialize a list of lists for each index, starting with (snap_id=-1, val=0) to represent initial state.
- On set(index, val), if last recorded snap_id is current snap_id, update value; else append (current snap_id, val).
- On snap(), increment snap_id and return previous snap_id.
- On get(index, snap_id), binary search in the list for the largest snap_id ≤ requested snap_id and return its value.
💡 The key is maintaining sorted snapshots per index and using binary search to quickly find the correct value.
import bisect
class SnapshotArray:
def __init__(self, length: int):
self.snap_id = 0
self.data = [[(-1, 0)] for _ in range(length)]
def set(self, index: int, val: int) -> None:
if self.data[index][-1][0] == self.snap_id:
self.data[index][-1] = (self.snap_id, val)
else:
self.data[index].append((self.snap_id, val))
def snap(self) -> int:
self.snap_id += 1
return self.snap_id - 1
def get(self, index: int, snap_id: int) -> int:
arr = self.data[index]
i = bisect.bisect_right(arr, (snap_id, float('inf'))) - 1
return arr[i][1]
# Driver code
if __name__ == '__main__':
snapshotArr = SnapshotArray(3)
snapshotArr.set(0,5)
snap_id = snapshotArr.snap()
snapshotArr.set(0,6)
print(snap_id) # Output: 0
print(snapshotArr.get(0, snap_id)) # Output: 5
Line Notes
self.data = [[(-1, 0)] for _ in range(length)]Initialize each index with a default snapshot (-1, 0) for initial zero values
if self.data[index][-1][0] == self.snap_id:If last change is in current snap_id, overwrite it to avoid duplicates
self.data[index].append((self.snap_id, val))Otherwise append new (snap_id, val) pair
self.snap_id += 1Increment snap_id on snap()
i = bisect.bisect_right(arr, (snap_id, float('inf'))) - 1Binary search to find rightmost snap_id ≤ requested snap_id
return arr[i][1]Return the value corresponding to found snap_id
import java.util.*;
class SnapshotArray {
private int snap_id;
private List<List<int[]>> data;
public SnapshotArray(int length) {
snap_id = 0;
data = new ArrayList<>();
for (int i = 0; i < length; i++) {
List<int[]> list = new ArrayList<>();
list.add(new int[]{-1, 0});
data.add(list);
}
}
public void set(int index, int val) {
List<int[]> list = data.get(index);
if (list.get(list.size() - 1)[0] == snap_id) {
list.get(list.size() - 1)[1] = val;
} else {
list.add(new int[]{snap_id, val});
}
}
public int snap() {
snap_id++;
return snap_id - 1;
}
public int get(int index, int snap_id) {
List<int[]> list = data.get(index);
int left = 0, right = list.size() - 1;
int res = 0;
while (left <= right) {
int mid = left + (right - left) / 2;
if (list.get(mid)[0] <= snap_id) {
res = list.get(mid)[1];
left = mid + 1;
} else {
right = mid - 1;
}
}
return res;
}
public static void main(String[] args) {
SnapshotArray snapshotArr = new SnapshotArray(3);
snapshotArr.set(0, 5);
int snap_id = snapshotArr.snap();
snapshotArr.set(0, 6);
System.out.println(snap_id); // Output: 0
System.out.println(snapshotArr.get(0, snap_id)); // Output: 5
}
}
Line Notes
list.add(new int[]{-1, 0});Initialize each index with default snapshot (-1, 0)
if (list.get(list.size() - 1)[0] == snap_id)Overwrite last value if same snap_id to avoid duplicates
list.add(new int[]{snap_id, val});Append new (snap_id, val) pair if different snap_id
snap_id++;Increment snap_id on snap()
while (left <= right)Binary search to find largest snap_id ≤ requested snap_id
res = list.get(mid)[1];Update result with value at mid if condition met
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class SnapshotArray {
vector<vector<pair<int,int>>> data;
int snap_id;
public:
SnapshotArray(int length) {
data = vector<vector<pair<int,int>>>(length, vector<pair<int,int>>(1, {-1,0}));
snap_id = 0;
}
void set(int index, int val) {
if (data[index].back().first == snap_id) {
data[index].back().second = val;
} else {
data[index].emplace_back(snap_id, val);
}
}
int snap() {
return snap_id++;
}
int get(int index, int s_id) {
auto &arr = data[index];
int left = 0, right = (int)arr.size() - 1;
int res = 0;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid].first <= s_id) {
res = arr[mid].second;
left = mid + 1;
} else {
right = mid - 1;
}
}
return res;
}
};
int main() {
SnapshotArray snapshotArr(3);
snapshotArr.set(0, 5);
int snap_id = snapshotArr.snap();
snapshotArr.set(0, 6);
cout << snap_id << endl; // Output: 0
cout << snapshotArr.get(0, snap_id) << endl; // Output: 5
return 0;
}
Line Notes
data = vector<vector<pair<int,int>>>(length, vector<pair<int,int>>(1, {-1,0}));Initialize each index with default snapshot (-1, 0)
if (data[index].back().first == snap_id)Overwrite last value if same snap_id to avoid duplicates
data[index].emplace_back(snap_id, val);Append new (snap_id, val) pair if different snap_id
return snap_id++;Return current snap_id then increment
while (left <= right)Binary search to find largest snap_id ≤ requested snap_id
res = arr[mid].second;Update result with value at mid if condition met
class SnapshotArray {
constructor(length) {
this.snap_id = 0;
this.data = Array.from({length}, () => [[-1, 0]]);
}
set(index, val) {
const arr = this.data[index];
if (arr[arr.length - 1][0] === this.snap_id) {
arr[arr.length - 1][1] = val;
} else {
arr.push([this.snap_id, val]);
}
}
snap() {
return this.snap_id++;
}
get(index, snap_id) {
const arr = this.data[index];
let left = 0, right = arr.length - 1;
let res = 0;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid][0] <= snap_id) {
res = arr[mid][1];
left = mid + 1;
} else {
right = mid - 1;
}
}
return res;
}
}
// Test code
const snapshotArr = new SnapshotArray(3);
snapshotArr.set(0, 5);
const snap_id = snapshotArr.snap();
snapshotArr.set(0, 6);
console.log(snap_id); // Output: 0
console.log(snapshotArr.get(0, snap_id)); // Output: 5
Line Notes
this.data = Array.from({length}, () => [[-1, 0]]);Initialize each index with default snapshot (-1, 0)
if (arr[arr.length - 1][0] === this.snap_id)Overwrite last value if same snap_id to avoid duplicates
arr.push([this.snap_id, val]);Append new (snap_id, val) pair if different snap_id
return this.snap_id++;Return current snap_id then increment
while (left <= right)Binary search to find largest snap_id ≤ requested snap_id
res = arr[mid][1];Update result with value at mid if condition met
TimeO(log m) per get, O(1) amortized per set and snap, where m is number of snaps
SpaceO(k) where k is number of set operations
Each set stores one entry. Snap is O(1). Get uses binary search on snapshots per index, making queries efficient.
💡 For 50,000 snaps, get takes about log2(50,000) ≈ 16 steps, which is very efficient.
Interview Verdict: Accepted and optimal for large inputs
This approach balances memory and speed, making it the best choice in interviews.