Snapshot Array — Solution in Python, Java & C++
Snapshot Array solution explained: step-by-step approach, complexity and code in Python, Java, C++ and JavaScript. A medium problem, asked at Google.
Snapshot Array is a medium coding problem on arrays, hash table and binary search, asked in Google interviews.
- Difficulty: Medium
- Topics: Arrays, Hash Table, Binary Search
- Asked at: Google
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
Implement an array that can take snapshots of itself and later read any element as it was in any snapshot.
Write the class SnapshotArray:
SnapshotArray(int length)creates an array oflengthelements, all0.void set(int index, int value)sets the element atindextovalue.int snap()takes a snapshot and returns its id: the number of timessnaphad been called before (so the first snapshot is0).int get(int index, int snapId)returns the value the element atindexhad when snapshotsnapIdwas taken.
The judge calls the methods in the order listed in the first input line, with the arguments in the second; the expected output holds each call's result, null for the constructor and for every method that returns nothing.
Example 1
Input: ["SnapshotArray","set","snap","set","get","get"]
[[3],[0,5],[],[0,6],[0,0],[1,0]]
Output: [null,null,0,null,5,0]
Explanation: Snapshot 0 is taken while element 0 is 5; setting it to 6 afterwards does not change snapshot 0. Element 1 was never set, so it is 0.
Example 2
Input: ["SnapshotArray","snap","set","set","snap","snap","get","get","get"]
[[2],[],[1,4],[1,7],[],[],[1,0],[1,1],[1,2]]
Output: [null,0,null,null,1,2,0,7,7]
Explanation: Snapshot 0 is taken before anything is set. Element 1 is set twice before snapshot 1, which keeps the last value, 7; snapshot 2 sees the same 7.
Constraints
1 <= length <= 5 * 10^40 <= index < length0 <= value <= 10^90 <= snapId < the number of snap calls made so farAt most 5 * 10^4 calls are made to set, snap and get
Hints for Snapshot Array
Hint 1
Copying the whole array on every snapshot costs O(length) per
snap— far too much. Most elements do not change between snapshots.Hint 2
Store, for each index, only its changes: a list of (snapshot id, value) pairs in the order they happened. Several
sets before the same snapshot overwrite one another.Hint 3
get(index, snapId)wants the last change toindexrecorded with an id<= snapId— binary search that index's list. No such change means the element was still0.
How to solve Snapshot Array
Record history per element instead of copying the array: each index keeps a list of (snapId, value) pairs, where snapId is the id the next snapshot will get at the time of the write.
Approach
- Keep a counter
snapId = 0and, for each index, an initially empty history list. set(index, value): if the index's last entry has idsnapId, overwrite its value; otherwise append(snapId, value).snap(): returnsnapIdand then increase it.get(index, snapId): binary search the index's history for the last entry with id<= snapIdand return its value, or0if there is none.
Why it works
A write made while the counter is s happens after snapshot s - 1 and before snapshot s, so it belongs to snapshot s and every later one until the element changes again. Within one id only the last write matters, which the overwrite keeps. The ids in each history are increasing, so the last entry with id <= snapId is the value the element held when that snapshot was taken.
Complexity
- Time —
O(1) per set and snap, O(log k) per get for an index changed k times - Space —
O(number of set calls)
Pitfalls
- Copying the array on
snapis O(length) per call — 2.5 × 10^9 steps at the limits. - Appending on every
setinstead of overwriting within the same id still works forgetif the search finds the last matching entry; finding the first one returns a stale value. - Untouched elements are
0, so an empty history (or one whose first change came later) answers0.
Reference solution
Python
import bisect
class SnapshotArray:
def __init__(self, length: int):
self.snap_id = 0
self.ids = {} # index -> snapshot ids of its changes, increasing
self.vals = {} # index -> the value written for each of those ids
def set(self, index: int, value: int) -> None:
ids = self.ids.setdefault(index, [])
vals = self.vals.setdefault(index, [])
if ids and ids[-1] == self.snap_id:
vals[-1] = value # a later write before the same snapshot wins
else:
ids.append(self.snap_id)
vals.append(value)
def snap(self) -> int:
self.snap_id += 1
return self.snap_id - 1
def get(self, index: int, snapId: int) -> int:
ids = self.ids.get(index)
if not ids:
return 0
i = bisect.bisect_right(ids, snapId) - 1
return self.vals[index][i] if i >= 0 else 0Java
class SnapshotArray {
private int snapId = 0;
private HashMap<Integer, TreeMap<Integer, Integer>> h = new HashMap<>();
public SnapshotArray(int length) {
}
public void set(int index, int value) {
// put() on the same snapshot id overwrites the earlier write
h.computeIfAbsent(index, k -> new TreeMap<>()).put(snapId, value);
}
public int snap() {
return snapId++;
}
public int get(int index, int snapId) {
TreeMap<Integer, Integer> t = h.get(index);
if (t == null) return 0;
Map.Entry<Integer, Integer> e = t.floorEntry(snapId);
return e == null ? 0 : e.getValue();
}
}C++
class SnapshotArray {
int snapId = 0;
vector<vector<pair<int, int>>> h; // per index: (snapshot id, value), ids increasing
public:
SnapshotArray(int length) : h(length) {}
void set(int index, int value) {
auto& v = h[index];
if (!v.empty() && v.back().first == snapId) v.back().second = value; // same snapshot: overwrite
else v.push_back({snapId, value});
}
int snap() {
return snapId++;
}
int get(int index, int snapId) {
auto& v = h[index];
auto it = upper_bound(v.begin(), v.end(), make_pair(snapId, INT_MAX));
return it == v.begin() ? 0 : prev(it)->second;
}
};JavaScript
var SnapshotArray = function(length) {
this.snapId = 0;
this.h = new Map(); // index -> { ids: [], vals: [] }
};
SnapshotArray.prototype.set = function(index, value) {
var e = this.h.get(index);
if (!e) { e = { ids: [], vals: [] }; this.h.set(index, e); }
var n = e.ids.length;
if (n > 0 && e.ids[n - 1] === this.snapId) e.vals[n - 1] = value; // same snapshot: overwrite
else { e.ids.push(this.snapId); e.vals.push(value); }
};
SnapshotArray.prototype.snap = function() {
return this.snapId++;
};
SnapshotArray.prototype.get = function(index, snapId) {
var e = this.h.get(index);
if (!e) return 0;
var lo = 0, hi = e.ids.length; // first id > snapId
while (lo < hi) {
var mid = (lo + hi) >> 1;
if (e.ids[mid] <= snapId) lo = mid + 1; else hi = mid;
}
return lo > 0 ? e.vals[lo - 1] : 0;
};Also on the editorial tab: C, C#, Go, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.
All 1199 arrays problems · the whole catalogue
Learn the technique: Arrays · Hashing: Hash Maps and Hash Sets