🧠
Better Approach (Binary Search on Sorted Timestamp List)
💡 This approach improves get operation by using binary search on the sorted list of timestamps, making retrieval efficient.
Intuition
Since timestamps are strictly increasing for each key, store them in a sorted list. Use binary search to find the largest timestamp <= query timestamp.
Algorithm
- Maintain a dictionary mapping keys to a list of (timestamp, value) pairs sorted by timestamp.
- For set operation, append the (timestamp, value) pair (timestamps are strictly increasing).
- For get operation, perform binary search on the timestamp list to find the rightmost timestamp <= query timestamp.
- Return the corresponding value or empty string if none found.
💡 The key insight is leveraging the sorted order of timestamps to quickly find the correct value.
import bisect
class TimeMap:
def __init__(self):
self.store = {}
def set(self, key: str, value: str, timestamp: int) -> None:
if key not in self.store:
self.store[key] = {'times': [], 'values': []}
self.store[key]['times'].append(timestamp)
self.store[key]['values'].append(value)
def get(self, key: str, timestamp: int) -> str:
if key not in self.store:
return ""
times = self.store[key]['times']
values = self.store[key]['values']
i = bisect.bisect_right(times, timestamp)
if i == 0:
return ""
return values[i-1]
# Driver code
if __name__ == "__main__":
tm = TimeMap()
tm.set("foo", "bar", 1)
print(tm.get("foo", 1)) # bar
print(tm.get("foo", 3)) # bar
tm.set("foo", "bar2", 4)
print(tm.get("foo", 4)) # bar2
print(tm.get("foo", 5)) # bar2
Line Notes
import bisectImport bisect module for binary search
self.store[key] = {'times': [], 'values': []}Store timestamps and values separately for efficient binary search
self.store[key]['times'].append(timestamp)Append timestamp preserving sorted order
i = bisect.bisect_right(times, timestamp)Find insertion point to the right of timestamp
if i == 0:No timestamp <= query timestamp found
return values[i-1]Return value corresponding to largest timestamp <= query
import java.util.*;
class TimeMap {
private Map<String, List<Integer>> timesMap;
private Map<String, List<String>> valuesMap;
public TimeMap() {
timesMap = new HashMap<>();
valuesMap = new HashMap<>();
}
public void set(String key, String value, int timestamp) {
timesMap.putIfAbsent(key, new ArrayList<>());
valuesMap.putIfAbsent(key, new ArrayList<>());
timesMap.get(key).add(timestamp);
valuesMap.get(key).add(value);
}
public String get(String key, int timestamp) {
if (!timesMap.containsKey(key)) return "";
List<Integer> times = timesMap.get(key);
List<String> values = valuesMap.get(key);
int i = Collections.binarySearch(times, timestamp);
if (i < 0) {
i = -i - 2;
}
if (i < 0) return "";
return values.get(i);
}
// Driver code
public static void main(String[] args) {
TimeMap tm = new TimeMap();
tm.set("foo", "bar", 1);
System.out.println(tm.get("foo", 1)); // bar
System.out.println(tm.get("foo", 3)); // bar
tm.set("foo", "bar2", 4);
System.out.println(tm.get("foo", 4)); // bar2
System.out.println(tm.get("foo", 5)); // bar2
}
}
Line Notes
timesMap = new HashMap<>();Map key to list of timestamps
valuesMap = new HashMap<>();Map key to list of values
timesMap.get(key).add(timestamp);Append timestamp preserving order
int i = Collections.binarySearch(times, timestamp);Binary search for timestamp
if (i < 0) { i = -i - 2; }Adjust index to find largest timestamp <= query
if (i < 0) return "";No valid timestamp found
#include <iostream>
#include <unordered_map>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
class TimeMap {
unordered_map<string, vector<int>> timesMap;
unordered_map<string, vector<string>> valuesMap;
public:
TimeMap() {}
void set(string key, string value, int timestamp) {
timesMap[key].push_back(timestamp);
valuesMap[key].push_back(value);
}
string get(string key, int timestamp) {
if (timesMap.find(key) == timesMap.end()) return "";
const vector<int> × = timesMap[key];
const vector<string> &values = valuesMap[key];
int i = (int)(upper_bound(times.begin(), times.end(), timestamp) - times.begin()) - 1;
if (i < 0) return "";
return values[i];
}
};
int main() {
TimeMap tm;
tm.set("foo", "bar", 1);
cout << tm.get("foo", 1) << "\n"; // bar
cout << tm.get("foo", 3) << "\n"; // bar
tm.set("foo", "bar2", 4);
cout << tm.get("foo", 4) << "\n"; // bar2
cout << tm.get("foo", 5) << "\n"; // bar2
return 0;
}
Line Notes
unordered_map<string, vector<int>> timesMap;Map key to vector of timestamps
unordered_map<string, vector<string>> valuesMap;Map key to vector of values
timesMap[key].push_back(timestamp);Append timestamp preserving order
int i = (int)(upper_bound(times.begin(), times.end(), timestamp) - times.begin()) - 1;Find rightmost timestamp <= query using upper_bound
if (i < 0) return "";No valid timestamp found
return values[i];Return value corresponding to found timestamp
class TimeMap {
constructor() {
this.store = new Map();
}
set(key, value, timestamp) {
if (!this.store.has(key)) {
this.store.set(key, {times: [], values: []});
}
this.store.get(key).times.push(timestamp);
this.store.get(key).values.push(value);
}
get(key, timestamp) {
if (!this.store.has(key)) return "";
const {times, values} = this.store.get(key);
let left = 0, right = times.length - 1;
let res = "";
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (times[mid] <= timestamp) {
res = values[mid];
left = mid + 1;
} else {
right = mid - 1;
}
}
return res;
}
}
// Driver code
const tm = new TimeMap();
tm.set("foo", "bar", 1);
console.log(tm.get("foo", 1)); // bar
console.log(tm.get("foo", 3)); // bar
tm.set("foo", "bar2", 4);
console.log(tm.get("foo", 4)); // bar2
console.log(tm.get("foo", 5)); // bar2
Line Notes
this.store = new Map();Initialize map from key to object with times and values arrays
this.store.get(key).times.push(timestamp);Append timestamp preserving order
let left = 0, right = times.length - 1;Initialize binary search boundaries
if (times[mid] <= timestamp)Check if mid timestamp is valid
res = values[mid];Update result with current valid value
left = mid + 1;Search right half for closer timestamp
TimeO(log n) per get, O(1) per set
SpaceO(n) for storing all values
Binary search reduces get from linear to logarithmic time. Set remains constant time.
💡 For n=100000, get operations now take about 17 steps instead of 100000, making it efficient.
Interview Verdict: Accepted / Efficient for large inputs
This approach is optimal for the problem constraints and is the recommended solution in interviews.