LeetCode ยท #981

Time Based Key-Value Store Solution

Design a data structure that stores key-value pairs with timestamps, and retrieves the value at a given key for the latest timestamp โ‰ค a query timestamp.

01
Section One ยท Problem

Problem Statement

๐Ÿท๏ธ

Difficulty

Medium

๐Ÿ”—

LeetCode

Problem #981

๐Ÿ—๏ธ

Pattern

Design โ€” HashMap + Binary Search

Implement a TimeMap class with: set(key, value, timestamp) โ€” stores the key-value pair at the given timestamp, and get(key, timestamp) โ€” returns the value with the largest timestamp โ‰ค the given timestamp, or "" if none exists. All set timestamps are strictly increasing.

Example
TimeMap tm = new TimeMap(); tm.set("foo", "bar", 1); tm.get("foo", 1); // "bar" tm.get("foo", 3); // "bar" (latest ts โ‰ค 3 is 1) tm.set("foo", "bar2", 4); tm.get("foo", 4); // "bar2" tm.get("foo", 5); // "bar2" (latest ts โ‰ค 5 is 4)
Constraints
โ€ข 1 โ‰ค key.length, value.length โ‰ค 100 โ€ข 1 โ‰ค timestamp โ‰ค 10โท โ€ข All set timestamps are strictly increasing for each key โ€ข At most 2 ร— 10โต calls to set and get
02
Section Two ยท Approach 1

Brute Force โ€” Linear Scan O(n)

For each get, scan all entries for the key from newest to oldest and return the first with timestamp โ‰ค query. O(n) per get where n is the number of entries for that key.

Problem: Linear scan per get with up to 2ร—10โต calls. Since timestamps are inserted in increasing order, the list is already sorted โ€” binary search gives O(log n) per get.
03
Section Three ยท Approach 2

HashMap + Binary Search โ€” O(log n) per get

Use a HashMap<String, List<(timestamp, value)>>. Since timestamps for each key are strictly increasing, the list is sorted. For get, binary search for the rightmost timestamp โ‰ค query (upper bound - 1).

๐Ÿ’ก Mental model: Think of a filing cabinet. Each key has its own folder with timestamped pages in chronological order. When someone asks "what was the value at time T?", you flip to the right page using binary search โ€” find the last page with a timestamp not exceeding T.
Algorithm
  • set(key, value, ts): Append (ts, value) to the key's list.
  • get(key, ts): Binary search the key's list for the rightmost entry where entry.ts โ‰ค ts.
  • If found โ†’ return the value. If not (all timestamps > query) โ†’ return "".
๐ŸŽฏ Why rightmost (upper bound)?:
  • We want the latest timestamp that doesn't exceed the query.
  • This is a "rightmost โ‰ค target" search โ€” equivalent to upper_bound(ts) - 1 in C++ or bisect_right(ts) - 1 in Python.
04
Section Four ยท Trace

Visual Walkthrough

Trace: set("foo","bar",1), set("foo","bar2",4), get("foo",3):

HashMap + Binary Search on timestamp list
map["foo"] = [(1,"bar"), (4,"bar2")] get("foo", 3): Binary search for rightmost ts โ‰ค 3 in [1, 4] L=0,R=2,mid=1 โ†’ ts[1]=4 > 3 โ†’ R=1 L=0,R=1,mid=0 โ†’ ts[0]=1 โ‰ค 3 โ†’ L=1 L==R==1 โ†’ index = 1-1 = 0 โ†’ value = "bar" Answer: "bar" โœ“
05
Section Five ยท Implementation

Code โ€” Java & Python

Java โ€” HashMap + Binary Search
import java.util.*; class TimeMap { private Map<String, List<int[]>> tsMap; // int[]{timestamp} private Map<String, List<String>> valMap; public TimeMap() { tsMap = new HashMap<>(); valMap = new HashMap<>(); } public void set(String key, String value, int timestamp) { tsMap.computeIfAbsent(key, k -> new ArrayList<>()).add(new int[]{timestamp}); valMap.computeIfAbsent(key, k -> new ArrayList<>()).add(value); } public String get(String key, int timestamp) { if (!tsMap.containsKey(key)) return ""; List<int[]> times = tsMap.get(key); int left = 0, right = times.size(); while (left < right) { // upper bound int mid = left + (right - left) / 2; if (times.get(mid)[0] <= timestamp) left = mid + 1; else right = mid; } if (left == 0) return ""; // all timestamps > query return valMap.get(key).get(left - 1); } }
Python โ€” defaultdict + bisect
from collections import defaultdict import bisect class TimeMap: def __init__(self): self.store = defaultdict(list) # key โ†’ [(ts, val), ...] def set(self, key: str, value: str, timestamp: int) -> None: self.store[key].append((timestamp, value)) def get(self, key: str, timestamp: int) -> str: entries = self.store[key] # bisect_right on timestamps โ€” find rightmost ts โ‰ค timestamp i = bisect.bisect_right(entries, (timestamp, chr(127))) if i == 0: return "" return entries[i - 1][1]
06
Section Six ยท Analysis

Complexity Analysis

OperationTimeSpace
setO(1) amortizedO(1) per call
get (linear scan)O(n)O(1)
get (binary search) โ† optimal O(log n) O(1)
Total space: O(S) where S = total number of set calls. Each call stores one (timestamp, value) pair.
07
Section Seven ยท Edge Cases

Edge Cases & Pitfalls

CaseScenarioWhy It Matters
Key not foundget("unknown", 1)Key doesn't exist in map โ†’ return "".
Timestamp too earlyset at ts=5, get at ts=3No entry โ‰ค 3 โ†’ return "".
Exact matchset at ts=5, get at ts=5Binary search finds exact timestamp โ†’ return its value.
Many entries10โต sets for one keyBinary search handles large lists in ~17 comparisons.
โš  Common Mistake: Using bisect_left instead of bisect_right (or equivalent). We want the rightmost entry โ‰ค timestamp. bisect_right finds the first index after all entries โ‰ค timestamp โ€” subtract 1 to get the answer. bisect_left would miss the exact-match case when there are additional concerns.

โ† Back to Binary Search problems