View on NeetCode
View on LeetCode

Problem

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 key key with the value value at the given time timestamp.
  • String get(String key, int timestamp) Returns a value such that set was called previously, with timestamp_prev <= timestamp. If there are multiple such values, it returns the value associated with the largest timestamp_prev. If there are no values, it returns "".

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, then 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 <= 100
  • key and value consist of lowercase English letters and digits.
  • 1 <= timestamp <= 10^7
  • All the timestamps timestamp of set are strictly increasing.
  • At most 2 * 10^5 calls will be made to set and get.

Solution

The key insight is to store a list of (timestamp, value) pairs for each key. Since timestamps are strictly increasing, the list is naturally sorted. We can use binary search to find the largest timestamp <= the query timestamp.

Data structure:

  • HashMap: key → list of (timestamp, value) pairs
  • For each key, the list is sorted by timestamp (guaranteed by strictly increasing timestamps)

Implementation

class TimeMap:
    def __init__(self):
        # key -> list of [timestamp, value]
        self.store = {}

    def set(self, key: str, value: str, timestamp: int) -> None:
        if key not in self.store:
            self.store[key] = []
        self.store[key].append([timestamp, value])

    def get(self, key: str, timestamp: int) -> str:
        if key not in self.store:
            return ""

        values = self.store[key]
        left, right = 0, len(values) - 1
        result = ""

        # Binary search for largest timestamp <= target
        while left <= right:
            mid = (left + right) // 2
            if values[mid][0] <= timestamp:
                result = values[mid][1]
                left = mid + 1  # Try to find a larger valid timestamp
            else:
                right = mid - 1

        return result

Alternative (Using bisect):

from bisect import bisect_right

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] = []
        self.store[key].append([timestamp, value])

    def get(self, key: str, timestamp: int) -> str:
        if key not in self.store:
            return ""

        values = self.store[key]
        # Find insertion point for timestamp
        i = bisect_right(values, [timestamp, chr(255)])

        # Return value at i-1 if it exists
        return values[i-1][1] if i > 0 else ""

Complexity Analysis

  • Time Complexity:
    • set: O(1), we append to the list
    • get: O(log n), where n is the number of timestamps for the key. We perform binary search.
  • Space Complexity: O(n), where n is the total number of set operations. We store all key-value-timestamp tuples.

Key Insights

  1. Natural Sorting: Since timestamps are strictly increasing, we don’t need to sort the list. Each new timestamp is automatically in the correct position when appended.

  2. Binary Search Target: We’re looking for the largest timestamp that is less than or equal to the query timestamp. This is a classic “find rightmost” binary search variant.

  3. Result Tracking: We track the result during binary search. When we find a valid timestamp (values[mid][0] <= timestamp), we save it as a potential answer and continue searching right for a larger valid timestamp.

  4. Empty Result: If we never find a valid timestamp (all timestamps are greater than query timestamp), we return an empty string.

  5. bisect Module: Python’s bisect_right can be used to find the insertion point. The value at index i-1 gives us the largest timestamp <= query timestamp.