981 - Time Based Key-Value Store
Difficulty: Medium | Pattern: Binary Search + HashMap | Company tags: Google, Amazon, Uber
Problem Statement
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 timetimestamp.String get(String key, int timestamp)Returns a value such thatsetwas called previously, withtimestamp_prev <= timestamp. If there are multiple such values, return the value associated with the largesttimestamp_prev. If there are no values, return"".
Example:
TimeMap map = new TimeMap();
map.set("foo", "bar", 1);
map.get("foo", 1) → "bar"
map.get("foo", 3) → "bar"
map.set("foo", "bar2", 4);
map.get("foo", 4) → "bar2"
map.get("foo", 5) → "bar2"
Algorithm Flow
Solution: HashMap + Binary Search — O(log n) per get
Key insight: Store (timestamp, value) pairs in a list per key. Since set is always called with increasing timestamps, the list is sorted. Use binary search to find the largest timestamp <= query timestamp.
from collections import defaultdict
import bisect
class TimeMap:
def __init__(self):
self.store = defaultdict(list) # key → [(timestamp, value)]
def set(self, key: str, value: str, timestamp: int) -> None:
self.store[key].append((timestamp, value))
def get(self, key: str, timestamp: int) -> str:
pairs = self.store[key]
# Binary search for largest timestamp <= given timestamp
lo, hi = 0, len(pairs) - 1
result = ""
while lo <= hi:
mid = (lo + hi) // 2
if pairs[mid][0] <= timestamp:
result = pairs[mid][1]
lo = mid + 1
else:
hi = mid - 1
return result
Dry Run
After set("foo","bar",1) and set("foo","bar2",4):
store["foo"] = [(1,"bar"), (4,"bar2")]
get("foo", 3):
- lo=0, hi=1, mid=0 → pairs[0]=(1,"bar") → 1 lte 3 → result="bar", lo=1
- lo=1, hi=1, mid=1 → pairs[1]=(4,"bar2") → 4 gt 3 → hi=0
- Loop ends → return "bar" ✓
get("foo", 5):
- mid=0 → 1 lte 5 → result="bar", lo=1
- mid=1 → 4 lte 5 → result="bar2", lo=2
- Loop ends → return "bar2" ✓
Edge Cases
- Key not in store → empty list, binary search returns ""
- Timestamp before any stored → return ""
- Multiple equal timestamps → problem guarantees timestamps are strictly increasing per call
Complexity
- Time: O(1) set, O(log n) get
- Space: O(n) total entries
Key Terms
| Term | Definition |
|---|---|
| Binary search | O(log n) search over a sorted sequence by repeatedly halving the search range. |
| Monotonic append | Property that set calls arrive with strictly increasing timestamps, keeping each key's list sorted without extra sorting. |
| HashMap of lists | Maps each key to its own sorted list of (timestamp, value) pairs. |
| Floor query | Finding the largest stored timestamp that is <= a target timestamp. |
| Amortized design | Trading O(1) writes for O(log n) reads by deferring ordering work to query time via binary search. |
FAQ
- Can this be solved without extra space? No — values across timestamps must be retained; O(n) total storage across all keys is unavoidable for correctness.
- What if timestamps could be inserted out of order? The per-key list would no longer be sorted, so binary search would require inserting in sorted position (O(n) insert) or re-sorting before queries.
- How would this change if we needed range queries instead of floor queries? You'd binary search both bounds and slice the list, or use a different structure like a sorted map with range iteration.
- What if
getis called far more often thanset? The current design already favors this — O(1) set and O(log n) get is optimal for read-heavy workloads. - Could a balanced BST or sorted container replace the list? Yes, e.g. Python's
sortedcontainers.SortedList, but a plain list plus binary search is simpler and equally efficient here since inserts are always at the end.
Quick Revision
- Store each key's values as a list of
(timestamp, value)pairs. setappends in O(1) since timestamps are guaranteed increasing.getbinary searches for the largest timestamp<=the query timestamp.- Track the best-found value as
loadvances past valid candidates. - If no valid timestamp exists, return
"". - Time: O(1) set, O(log n) get; Space: O(n) total entries.
- Core pattern: HashMap + binary search for "floor" lookups on sorted data.
- Generalizes to any "latest value at or before time T" query.
Related Problems
- 1146 - Snapshot Array
- 704 - Binary Search
- 35 - Search Insert Position