Time Based Key Value Store
Problem Description
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 keykeywith the valuevalueat the given time stamptimestamp.String get(String key, int timestamp)Returns a value such thatsetwas called previously, withtimestamp_prev <= timestamp. If there are multiple such values, it returns the value associated with the largesttimestamp_prev. If there are no such values, it returns"".
Examples
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, 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 <= 100keyandvalueconsist of lowercase English letters and digits.1 <= timestamp <= 10⁷- All the timestamps in
setare strictly increasing. - At most
2 * 10⁵calls will be made tosetandget.
Mapping Keys to Sorted Timestamp Arrays
Since the timestamps in set calls are strictly increasing, they are naturally sorted.
We can map each key to a list of pairs (timestamp, value) in a hash map.
When calling get(key, timestamp):
- Retrieve the list of pairs for the key from the map.
- If no list exists, return
"". - Run binary search on the list to find the largest timestamp that is less than or equal to the query
timestamp. Return the corresponding value.
Solution 1: Hash Map with Binary Search on Timestamps
Store elements in list arrays inside a hash map, query values using binary search.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
class TimeMap {
private static class DataNode {
int timestamp;
String value;
DataNode(int timestamp, String value) {
this.timestamp = timestamp;
this.value = value;
}
}
private HashMap<String, List<DataNode>> map;
public TimeMap() {
map = new HashMap<>();
}
public void set(String key, String value, int timestamp) {
map.putIfAbsent(key, new ArrayList<>());
map.get(key).add(new DataNode(timestamp, value));
}
public String get(String key, int timestamp) {
if (!map.containsKey(key)) return "";
List<DataNode> list = map.get(key);
// Binary search for the largest timestamp <= query timestamp
int left = 0;
int right = list.size() - 1;
String res = "";
while (left <= right) {
int mid = left + (right - left) / 2;
if (list.get(mid).timestamp <= timestamp) {
res = list.get(mid).value; // record potential candidate
left = mid + 1; // look for larger timestamps
} else {
right = mid - 1;
}
}
return res;
}
}class TimeMap:
def __init__(self):
self.map = {} # maps key to a list of (timestamp, value)
def set(self, key: str, value: str, timestamp: int) -> None:
if key not in self.map:
self.map[key] = []
self.map[key].append((timestamp, value))
def get(self, key: str, timestamp: int) -> str:
if key not in self.map:
return ""
values = self.map[key]
left, right = 0, len(values) - 1
res = ""
while left <= right:
mid = left + (right - left) // 2
if values[mid][0] <= timestamp:
res = values[mid][1] # record potential candidate
left = mid + 1
else:
right = mid - 1
return res#include <unordered_map>
#include <vector>
#include <string>
class TimeMap {
private:
struct DataNode {
int timestamp;
std::string value;
};
std::unordered_map<std::string, std::vector<DataNode>> map;
public:
TimeMap() {}
void set(std::string key, std::string value, int timestamp) {
map[key].push_back({timestamp, value});
}
std::string get(std::string key, int timestamp) {
if (map.find(key) == map.end()) return "";
const auto& list = map[key];
int left = 0;
int right = list.size() - 1;
std::string res = "";
while (left <= right) {
int mid = left + (right - left) / 2;
if (list[mid].timestamp <= timestamp) {
res = list[mid].value;
left = mid + 1;
} else {
right = mid - 1;
}
}
return res;
}
};Complexity Analysis
- Time Complexity:
settakes O(1) time.gettakes O(log n) time where n is the number of values stored for the query key. - Space Complexity: O(N) auxiliary space to store all key-value entries in the hash map.
Where It Breaks
If the timestamp inputs are not sorted during set calls, this binary search method fails. Under those conditions, we would have to sort the lists before running queries (taking O(n log n)) or use self-balancing search trees (like TreeMap in Java).
Common Mistakes
- Incorrect check conditions: Using strict
<instead of<=when comparing timestamps. The problem requires returning values where the timestamp is less than or equal to the query. - Loop termination: Returning an empty string immediately if the first element is larger than target, which is correct, but failing to continue looking for larger matching candidates when a valid match is found.
Frequently Asked Questions
Why does this count as version control? By mapping timestamps to values, we can reconstruct the exact state of any key at any point in history.
What happens if multiple entries have the same timestamp?
The constraints guarantee that timestamps in set are strictly increasing, preventing duplicate timestamps for a single key.