
Problem Statement
The task is to implement a data structure called SnapshotArray
. This array-like structure supports special operations beyond normal array functionalities:
Initialization:
SnapshotArray(int length)
: Constructs the SnapshotArray with specifiedlength
. Each element in the array is initialized to0
.
Setting Values:
void set(index, val)
: Assigns the valueval
to the element at the specifiedindex
.
Snapshot Creation:
int snap()
: This operation captures the current state of the array and saves it as a snapshot. It then returns a unique identifier for this snapshot, termed assnap_id
, which is the count of total snapshots taken so far minus one.
Value Retrieval:
int get(index, snap_id)
: Fetches the value at a specificindex
from the snapshot identified bysnap_id
.
The purpose of this data structure is to efficiently track changes over time and enable the retrieval of past versions of the data array without altering the current state.
Examples
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 * 10^4
0 <= index < length
0 <= val <= 10^9
0 <= snap_id <
(total number of calls tosnap()
)- At most
5 * 10^4
calls will be made toset
,snap
, andget
.
Approach and Intuition
The SnapshotArray
functions as a version-controlled array, enabling not only modification of current values but also access to past configurations. The main challenge is to efficiently implement the set
and get
operations as they relate to snapshot history.
Efficient Implementation Strategy
Initialization:
- Start with an internal structure to represent each index using a sorted map (e.g., a dictionary of snap_id → value).
- Maintain a
snap_id
counter initialized to0
.
Setting Values:
- For every
set(index, val)
, update the internal map for the currentsnap_id
only at that index.
- For every
Snapshot Creation:
- For
snap()
, increment thesnap_id
counter and return the previous value. - No need to duplicate the array; we only track deltas.
- For
Value Retrieval:
- To
get(index, snap_id)
, use binary search (or bisect) to find the closest snapshot at or beforesnap_id
in that index’s history.
- To
This approach ensures O(log N) retrieval and O(1) mutation in practice, and memory is efficiently used by storing only changes rather than full snapshots.
Solutions
- C++
class SnapshotManager {
public:
int currentSnapId;
vector<vector<pair<int, int>>> snapshots;
SnapshotManager(int length) {
currentSnapId = 0;
snapshots.resize(length);
for (int i = 0; i < length; ++i) {
snapshots[i].push_back({0, 0});
}
}
void assign(int index, int value) {
snapshots[index].push_back({currentSnapId, value});
}
int snapshot() {
return currentSnapId++;
}
int fetch(int index, int snap_id) {
auto iter = upper_bound(snapshots[index].begin(), snapshots[index].end(), make_pair(snap_id, INT_MAX));
return prev(iter)->second;
}
};
The provided C++ solution implements a snapshot array system using a class named SnapshotManager
. This class provides functionalities to manage snapshot versions of array values. Here's an overview:
The constructor
SnapshotManager(int length)
initializes a 2D vector 'snapshots' with a given length. Each element in the array starts with an initial snapshot of the value zero.The
assign(int index, int value)
function adds a pair consisting of the current snapshot ID and the value to be assigned to thesnapshots
vector at the specified index.The
snapshot()
function increments and returns the current snapshot ID, signaling the creation of a new snapshot.The
fetch(int index, int snap_id)
function retrieves a value from thesnapshots
at a given index corresponding to a specified snapshot ID. It employs binary search viaupper_bound
to efficiently find the closest earlier snapshot and return its value.
The design demonstrates how to efficiently deal with a mutable state that needs to be versioned and accessed in constant snapshot states. The use of pairs to store values along with their respective snapshot IDs, combined with vector resizing and binary search, highlights a balance between time complexity and functionality in managing snapshot states of a dynamically changing array.
- Java
class SnapshotArray {
int snapshotId = 0;
TreeMap<Integer, Integer>[] stateHistory;
public SnapshotArray(int size) {
stateHistory = new TreeMap[size];
for (int i = 0; i < size; i++) {
stateHistory[i] = new TreeMap<Integer, Integer>();
stateHistory[i].put(0, 0);
}
}
public void set(int index, int value) {
stateHistory[index].put(snapshotId, value);
}
public int snap() {
return snapshotId++;
}
public int get(int index, int snapId) {
return stateHistory[index].floorEntry(snapId).getValue();
}
}
The provided Java solution demonstrates how to implement the Snapshot Array operation. The core data structure used is an array of TreeMap<Integer, Integer>
where each TreeMap
maps a snapshot ID to a value.
Here are the essential functions and how they operate:
Constructor
SnapshotArray(int size)
: This initializes an array ofTreeMap
, one for each index in the snapshot array. Initially, each map contains an entry setting the 0th snapshot value to 0.Method
set(int index, int value)
: Updates the value at a specific index for the current snapshot ID. This means appending the current value to theTreeMap
at the given index.Method
snap()
: Increases the snapshot ID by one and returns the previous snapshot ID. Eachsnap()
call represents a recording point for all precedingset()
operations.Method
get(int index, int snapId)
: Retrieves the value from the snapshot array at a specific index for a specific snapshot ID. The method uses thefloorEntry(snapId)
method on theTreeMap
to find the closest snapshot entry that is less than or equal to the given snapshot ID.
This solution effectively allows tracking the history of values at different indices over multiple snapshots without having to copy the entire array's content with each snapshot, resulting in a more space-efficient implementation. Each method in the class provides functionality that synchronizes with operations distributed over time and snapshots, allowing temporal navigation and mutation state recall at specific points.
- Python
class SnapshotArray:
def __init__(self, length: int):
self.snapshot_id = 0
self.snap_history = [[[0, 0]] for _ in range(length)]
def set(self, index: int, val: int) -> None:
self.snap_history[index].append([self.snapshot_id, val])
def snap(self) -> int:
self.snapshot_id += 1
return self.snapshot_id - 1
def get(self, index: int, snap_id: int) -> int:
history_index = bisect.bisect_right(self.snap_history[index], [snap_id, 10 ** 9])
return self.snap_history[index][history_index - 1][1]
The provided Python code implements a class called SnapshotArray
which simulates a snapshot array data structure. Here's how each component functions:
The initialization method
__init__
sets up the array. Each entry in the array starts with a snapshot history containing a single snapshot with ID 0 and initial value 0.The
set
method adds or updates the value at a specific index for the current snapshot. Every time you update an index, it records this value with the current snapshot ID.The
snap
method increments the snapshot ID and returns the previous snapshot ID. This method effectively simulates taking a snapshot of the array by making the next set of modifications trackable under a new snapshot ID.The
get
method fetches the value at a given index as of the specified snapshot ID. It uses binary search to find the closest value that doesn't exceed the requested snapshot ID.
Use this class to trace changes in the array state over discrete snapshots, preserving the history up to the most recent snapshot taken. It enables access to any past state without affecting current insertion and access speeds significantly. This system is particularly useful in scenarios where historical data retrieval is as important as current data modifications.
No comments yet.