Based on my comprehensive research, I can now provide the problem definition. The "Snapshot Key-Value Store" is a design problem documented in Apple and Rubrik interviews, though there isn't an official LeetCode listing. Let me create the final output.
Design a key-value data structure that allows users to store and retrieve values associated with keys, with the additional capability to take point-in-time snapshots of the entire data structure. After a snapshot is taken, any subsequent modifications to the key-value store should not affect previously captured snapshots. Users must be able to read values from any snapshot they have taken, and optionally delete snapshots to free up resources.
The data structure should support an arbitrary number of snapshots, where each snapshot preserves the complete state of the key-value store at the moment it was created. Modifications made after a snapshot is taken should be isolated from that snapshot—meaning queries against a snapshot return values as they existed at the time of the snapshot, regardless of changes made afterward.
SnapshotKeyValueStore()
void set(String key, String value)
String get(String key)
String takeSnapshot() (or int takeSnapshot())
String get(String key, String snapshotId) (or with snapshot identifier)
void deleteSnapshot(String snapshotId)
Input:
["SnapshotKeyValueStore", "set", "get", "takeSnapshot", "set", "get", "get"] [[], ["name", "Alice"], ["name"], [], ["name", "Bob"], ["name"], ["name", "snapshot1"]]
Output:
[null, null, "Alice", "snapshot1", null, "Bob", "Alice"]
Explanation:
set("name", "Alice") – Store name → Aliceget("name") – Returns "Alice" from current storetakeSnapshot() – Create snapshot labeled "snapshot1" with state {name: Alice}set("name", "Bob") – Update current store, name → Bobget("name") – Returns "Bob" from current store (modified after snapshot)get("name", "snapshot1") – Returns "Alice" (value from snapshot1, unaffected by the subsequent update)Input:
["SnapshotKeyValueStore", "set", "set", "takeSnapshot", "set", "takeSnapshot", "get", "get", "deleteSnapshot", "get"] [[], ["user", "John"], ["age", "30"], [], ["user", "Jane"], [], ["user", "snap1"], ["user", "snap2"], ["snap1"], ["user", "snap2"]]
Output:
[null, null, null, "snap1", null, "snap2", "John", "Jane", null, "Jane"]
Explanation:
set("user", "John") – current state: {user: John}set("age", "30") – current state: {user: John, age: 30}takeSnapshot() – Create "snap1" with {user: John, age: 30}set("user", "Jane") – current state: {user: Jane, age: 30}takeSnapshot() – Create "snap2" with {user: Jane, age: 30}get("user", "snap1") – Returns "John" (snap1 captured before update)get("user", "snap2") – Returns "Jane" (snap2 captured after update)deleteSnapshot("snap1") – Delete snap1get("user", "snap2") – Returns "Jane" (snap2 still valid, unaffected by snap1 deletion)Use a hash map to store the current key-value pairs. For each snapshot, instead of copying the entire hash map (which is space-inefficient), maintain a mapping from snapshot ID to only the key-value pairs that were modified before or at that snapshot. For queries against a snapshot, check the snapshot's stored changes first; if a key was not modified before the snapshot, fall back to the current state. Alternatively, use a versioning strategy where each key maintains a history of values with associated snapshot timestamps, enabling efficient retrieval without full copies. Copy-on-write or reference counting techniques can further optimize memory usage by sharing unchanged data across snapshots.