Design a time-based key-value data structure that can store multiple values for the same key at different time stamps and retrieve the key's value at a certain timestamp.
Implement the TimeMap
class:
TimeMap()
Initializes the object of the data structure.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 a value such that set
was called previously, with timestamp_prev <= timestamp
. If there are multiple such values, it returns the value associated with the largest timestamp_prev
. If there are no values, it returns ""
.Example 1:
Input ["TimeMap", "set", "get", "get", "set", "get", "get"] [[], ["foo", "bar", 1], ["foo", 1], ["foo", 3], ["foo", "bar2", 4], ["foo", 4], ["foo", 5]] Output [null, null, "bar", "bar", null, "bar2", "bar2"] Explanation TimeMap timeMap = new TimeMap(); timeMap.set("foo", "bar", 1); // store the key "foo" and value "bar" along with timestamp = 1. timeMap.get("foo", 1); // return "bar" timeMap.get("foo", 3); // return "bar", since there is no value corresponding to foo at timestamp 3 and timestamp 2, then the only value is at timestamp 1 is "bar". timeMap.set("foo", "bar2", 4); // store the key "foo" and value "bar2" along with timestamp = 4. timeMap.get("foo", 4); // return "bar2" timeMap.get("foo", 5); // return "bar2"
Constraints:
1 <= key.length, value.length <= 100
key
and value
consist of lowercase English letters and digits.1 <= timestamp <= 107
timestamp
of set
are strictly increasing.2 * 105
calls will be made to set
and get
.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force approach is the simplest way to solve this problem. It involves checking every possible timestamp to find the one that's closest to, but not after, the given timestamp.
Here's how the algorithm would work step-by-step:
class TimeMap:
def __init__(self):
self.key_value_store = {}
def set(self, key, value, timestamp):
if key not in self.key_value_store:
self.key_value_store[key] = []
self.key_value_store[key].append((value, timestamp))
def get(self, key, timestamp):
if key not in self.key_value_store:
return ""
closest_value = ""
# Iterate through all the values to find the closest one.
for stored_value, stored_timestamp in self.key_value_store[key]:
#Check to see if stored timestamp is less than or equal to
if stored_timestamp <= timestamp:
closest_value = stored_value
# Return the closest value or empty string.
return closest_value
The key to efficiently storing time-based data is leveraging the fact that the timestamps are ordered. We will store each key's values along with their timestamps, and when asked for a value at a specific time, we'll quickly find the closest timestamp without checking everything.
Here's how the algorithm would work step-by-step:
class TimeMap:
def __init__(self):
self.key_map = {}
def set(self, key: str, value: str, timestamp: int) -> None:
if key not in self.key_map:
self.key_map[key] = []
# Timestamps are always added in ascending order.
self.key_map[key].append((timestamp, value))
def get(self, key: str, timestamp: int) -> str:
if key not in self.key_map:
return ""
time_value_pairs = self.key_map[key]
# Binary search for the largest timestamp <= given timestamp.
left_pointer, right_pointer = 0, len(time_value_pairs) - 1
result = ""
while left_pointer <= right_pointer:
middle_pointer = (left_pointer + right_pointer) // 2
if time_value_pairs[middle_pointer][0] <= timestamp:
# This timestamp is valid so far, try to find a better one.
result = time_value_pairs[middle_pointer][1]
left_pointer = middle_pointer + 1
else:
# The timestamp is too large, move to the left half.
right_pointer = middle_pointer - 1
return result
Case | How to Handle |
---|---|
Null or empty key input to set or get | Return an empty string for get and do nothing for set or throw an IllegalArgumentException. |
Null or empty value input to set | Store an empty string for the value, or throw an IllegalArgumentException based on the specific problem requirements. |
Zero or negative timestamp in set or get | Treat zero as a valid timestamp and negative timestamps should be invalid, throwing an IllegalArgumentException. |
Very large timestamp in set or get (potential overflow) | Ensure the timestamp data type (e.g., long, int) can handle the magnitude of timestamps, or throw an exception if overflow occurs. |
Multiple set operations for the same key and timestamp | Overwrite the previous value for that key and timestamp, which is the expected behavior. |
Get operation with a timestamp smaller than any timestamp for a given key | Return an empty string since no value exists at or before that timestamp. |
Large number of set operations for the same key, requiring efficient search during get | Use a data structure like a sorted list or binary search tree to maintain timestamps and values for efficient searching (e.g., binary search). |
Get operation requests exact match for a timestamp that doesn't exist, but other values with same key exist | Return the value associated with the greatest timestamp that is less than the query timestamp. |