Implement a time-based key-value data structure that supports:
Implement the TimeMap class:
TimeMap() Initializes the object.void set(String key, String value, int timestamp) Stores the key key with the value value at the given time timestamp.String get(String key, int timestamp) Returns the most recent value of key if set was previously called on it and the most recent timestamp for that key prev_timestamp is less than or equal to the given timestamp (prev_timestamp <= timestamp). If there are no values, it returns "".Note: For all calls to set, the timestamps are in strictly increasing order.
Example 1:
Input:
["TimeMap", "set", ["alice", "happy", 1], "get", ["alice", 1], "get", ["alice", 2], "set", ["alice", "sad", 3], "get", ["alice", 3]]
Output:
[null, null, "happy", "happy", null, "sad"]
Explanation:
TimeMap timeMap = new TimeMap();
timeMap.set("alice", "happy", 1); // store the key "alice" and value "happy" along with timestamp = 1.
timeMap.get("alice", 1); // return "happy"
timeMap.get("alice", 2); // return "happy", there is no value stored for timestamp 2, thus we return the value at timestamp 1.
timeMap.set("alice", "sad", 3); // store the key "alice" and value "sad" along with timestamp = 3.
timeMap.get("alice", 3); // return "sad"Constraints:
1 <= key.length, value.length <= 100key and value only include lowercase English letters and digits.1 <= timestamp <= 1000
You should aim for a solution with O(1) time for set(), O(logn) time for get(), and O(m * n) space, where n is the total number of values associated with a key, and m is the total number of keys.
Can you think of a data structure that is useful for storing key-value pairs? Perhaps a hash-based data structure where we not only store unique elements but also associate additional information with each element?
We store key-value pairs in a hash map. In this case, we store the keys as usual, but instead of a single value, we store a list of values, each paired with its corresponding timestamp. This allows us to implement the set() method in O(1). How can you leverage this hash map to implement the get() method?
A brute force solution would involve linearly going through every value associated with the key and returning the most recent value with a timestamp less than or equal to the given timestamp. This approach has a time complexity of O(n) for each get() call. Can you think of a better way? Since the timestamps in the value list are sorted in ascending order by default, maybe an efficient searching algorithm could help.
We can use binary search because the timestamps in the values list are sorted in ascending order. This makes it straightforward to find the value with the most recent timestamp that is less than or equal to the given timestamp.
Before attempting this problem, you should be comfortable with:
We want to store values for a key along with timestamps, and when someone asks for a value at a given time, we must return the latest value set at or before that timestamp.
The brute-force idea is:
store everything as-is, and while retrieving, look through all timestamps for that key and pick the best match.
It's easy to implement, but slow because we scan all timestamps every time we call get().
class TimeMap:
def __init__(self):
self.keyStore = {}
def set(self, key: str, value: str, timestamp: int) -> None:
if key not in self.keyStore:
self.keyStore[key] = {}
if timestamp not in self.keyStore[key]:
self.keyStore[key][timestamp] = []
self.keyStore[key][timestamp].append(value)
def get(self, key: str, timestamp: int) -> str:
if key not in self.keyStore:
return ""
seen = 0
for time in self.keyStore[key]:
if time <= timestamp:
seen = max(seen, time)
return "" if seen == 0 else self.keyStore[key][seen][-1]Where is the total number of unique timestamps associated with a key and is the total number of keys.
For each key, we store all its (timestamp, value) pairs in sorted order by timestamp.
When we call get(key, timestamp), we don’t want to scan everything.
Instead, we want to quickly find the largest timestamp ≤ given timestamp for that key.
Because the timestamps are sorted, we can use binary search to find this position in O(log n) time:
"".So the idea is:
key -> sorted list of (timestamp, value) (or two parallel arrays: one for timestamps, one for values).set(key, value, timestamp):(timestamp, value) into the list for that key, keeping timestamps in sorted order.get(key, timestamp):key does not exist, return "".times be the sorted list of timestamps for this key.times to find the rightmost index i such that times[i] ≤ timestamp.times[i]."" (no value was set at or before that time).from sortedcontainers import SortedDict
class TimeMap:
def __init__(self):
self.m = defaultdict(SortedDict)
def set(self, key: str, value: str, timestamp: int) -> None:
self.m[key][timestamp] = value
def get(self, key: str, timestamp: int) -> str:
if key not in self.m:
return ""
timestamps = self.m[key]
idx = timestamps.bisect_right(timestamp) - 1
if idx >= 0:
closest_time = timestamps.iloc[idx]
return timestamps[closest_time]
return ""Where is the total number of values associated with a key and is the total number of keys.
Each key stores its values in the order they were inserted, and timestamps are guaranteed to be increasing for each key.
This means we can keep a simple list of (value, timestamp) pairs for every key.
To answer a get(key, timestamp) query, we only need to find the latest timestamp that is ≤ the given timestamp.
Because timestamps are sorted, we can use binary search to quickly find this position instead of scanning everything.
This gives an efficient and clean approach:
store values in arrays, then binary-search timestamps when retrieving.
key → list of [value, timestamp]set(key, value, timestamp):[value, timestamp] to the key’s list.get(key, timestamp):"".arr be the list of [value, timestamp] pairs.t ≤ timestamp."".class TimeMap:
def __init__(self):
self.keyStore = {} # key : list of [val, timestamp]
def set(self, key: str, value: str, timestamp: int) -> None:
if key not in self.keyStore:
self.keyStore[key] = []
self.keyStore[key].append([value, timestamp])
def get(self, key: str, timestamp: int) -> str:
res, values = "", self.keyStore.get(key, [])
l, r = 0, len(values) - 1
while l <= r:
m = (l + r) // 2
if values[m][1] <= timestamp:
res = values[m][0]
l = m + 1
else:
r = m - 1
return resWhere is the total number of values associated with a key and is the total number of keys.
A common mistake is searching for an exact timestamp match instead of finding the largest timestamp less than or equal to the query. Binary search should find the rightmost value satisfying timestamp <= query, not an exact match. If no exact match exists but earlier timestamps do, returning an empty string is incorrect.
Binary search boundaries are tricky. Using bisect_left instead of bisect_right, or not adjusting the index after the search, leads to returning values from timestamps greater than the query. Always verify your binary search returns the correct floor value by testing edge cases like querying before any set operation.
When a key exists but all stored timestamps are greater than the query timestamp, the correct behavior is to return an empty string. Some implementations incorrectly return the earliest stored value instead. Always check that your found index is valid (non-negative) before accessing the value.