Design a key-value store that remembers every version of a value together with its timestamp. set appends in order; get uses binary search to locate the most recent value at or before the queried timestamp in O(log n) time.
Implement a TimeMap class with two operations. set(key, value, timestamp) stores a value under a key at a given time. get(key, timestamp) returns the value most recently set at or before that timestamp, or "" if none exists. The problem guarantees timestamps passed to set are strictly increasing for each key.
Worked example. After:
set("love", "10", 1)set("love", "20", 2)Then get("love", 1) → "10", get("love", 3) → "20" (closest past version), get("love", 0) → "" (no version yet at ts=0).
setis called with strictly increasing timestamps, each key's list is automatically sorted by time — no sorting step needed. That means get can binary-search for the largest timestamp ≤ query (the rightmost valid entry) in O(log n), instead of scanning the whole history.Map<string, {ts, val}[]> holds one append-only list per key.{ ts: timestamp, val: value }. Because timestamps are strictly increasing the list stays sorted — O(1) amortized."".lo <= hi, compute mid. If list[mid].ts <= timestamp, record ans = mid and advance lo right (try to find something even closer). Otherwise pull hi left.ans === -1 ? "" : list[ans].val. The -1 sentinel means every stored timestamp was larger than the query.list[mid].ts <= timestamp (not strict <). Using strict less-than would skip exact timestamp matches and return the wrong earlier value. An exact match is always a valid answer and should be recorded as a candidate before searching further right.Space: O(total set calls) — each call appends one entry. No deduplication needed because each timestamp is unique per key.
1class TimeMap {2 private store: Map<string, { ts: number; val: string }[]>;34▶ constructor() {5▶ this.store = new Map();6 }78 set(key: string, value: string, timestamp: number): void {9 if (!this.store.has(key)) this.store.set(key, []);10 this.store.get(key)!.push({ ts: timestamp, val: value });11 // timestamps arrive in strictly increasing order — no sorting needed12 }1314 get(key: string, timestamp: number): string {15 const list = this.store.get(key);16 if (!list || list.length === 0) return '';1718 // Binary search: find the largest ts <= timestamp19 let lo = 0, hi = list.length - 1, ans = -1;20 while (lo <= hi) {21 const mid = Math.floor((lo + hi) / 2);22 if (list[mid].ts <= timestamp) {23 ans = mid; // valid candidate; try to find a larger one on the right24 lo = mid + 1;25 } else {26 hi = mid - 1; // too large; look left27 }28 }29 return ans === -1 ? '' : list[ans].val;30 }31}
class TimeMap {
private store: Map<string, { ts: number; val: string }[]>;
constructor() {
this.store = new Map();
}
set(key: string, value: string, timestamp: number): void {
if (!this.store.has(key)) this.store.set(key, []);
this.store.get(key)!.push({ ts: timestamp, val: value });
// timestamps arrive in strictly increasing order — no sorting needed
}
get(key: string, timestamp: number): string {
const list = this.store.get(key);
if (!list || list.length === 0) return '';
// Binary search: find the largest ts <= timestamp
let lo = 0, hi = list.length - 1, ans = -1;
while (lo <= hi) {
const mid = Math.floor((lo + hi) / 2);
if (list[mid].ts <= timestamp) {
ans = mid; // valid candidate; try to find a larger one on the right
lo = mid + 1;
} else {
hi = mid - 1; // too large; look left
}
}
return ans === -1 ? '' : list[ans].val;
}
}store maps each key to an array of { ts, val } pairs. One array per key, entries arriving in timestamp order."" immediately.ans tracks the index of the best candidate seen so far. When list[mid].ts <= timestamp we have a valid candidate and push lo right to hunt for a larger valid one. When list[mid].ts > timestamp we pull hi left. After the loop ans holds the rightmost valid index, or -1 if none.ans === -1 means all stored timestamps exceed the query — return "". Otherwise return the value at list[ans].val.set: O(1) amortized (array append). get: O(log n) per key list of length n. Total space: O(S) where S = total number of set calls.list[ans] instead of list[ans].val.| "most recent value at or before timestamp" | floor binary search on sorted list |
| set guarantees increasing timestamps | free sorted order → O(1) insert, O(log n) lookup |
| "design a versioned / time-traveling store" | Map<key, {ts,val}[]> + binary search |
| "largest X not exceeding Y" in sorted array | floor binary search (lo/hi/ans pattern) |
class TimeMap {
private store: Map<string, { ts: number; val: string }[]>;
constructor() { this.store = new Map(); }
set(key: string, value: string, timestamp: number): void {
if (!this.store.has(key)) this.store.set(key, []);
this.store.get(key)!.push({ ts: timestamp, val: value });
}
get(key: string, timestamp: number): string {
const list = this.store.get(key);
if (!list || !list.length) return '';
let lo = 0, hi = list.length - 1, ans = -1;
while (lo <= hi) {
const mid = Math.floor((lo + hi) / 2);
if (list[mid].ts <= timestamp) { ans = mid; lo = mid + 1; }
else hi = mid - 1;
}
return ans === -1 ? '' : list[ans].val;
}
}get("love", 3) return after set("love", "10", 1) and set("love", "20", 2)?get("love", 0) return after those same two set calls?