Implement string reduction and time map
Company: Grammarly
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: easy
Interview Round: Technical Screen
Quick Answer: This question evaluates skills in string-processing algorithms and time-based data structure design, measuring competency in correctness, efficiency, and handling repeated pattern removals and temporal key-value retrieval.
String Reduction with K Adjacent Duplicates
Constraints
- 1 <= len(s) (the empty string is also valid input and returns the empty string)
- 2 <= k
- s consists of lowercase English letters
- Removals cascade: characters made adjacent by a removal may form a new group of size k
Examples
Input: ("deeedbbcccbdaa", 3)
Expected Output: "aa"
Explanation: Remove 'eee', then 'ccc', leaving 'ddbbbdaa'; remove the merged 'bbb', leaving 'dddaa'; remove 'ddd', leaving 'aa'.
Input: ("abbaca", 2)
Expected Output: "ca"
Explanation: k=2 basic case: remove 'bb' to get 'aaca', remove 'aa' to get 'ca'.
Hints
- Brute-force re-scanning after every removal is O(n^2/k). Maintain run lengths instead so each character is processed once.
- Keep a stack of (character, run_count). When the next character equals the top, increment its count; otherwise push a fresh (character, 1).
- As soon as a run_count hits exactly k, pop that entry — this automatically merges the now-adjacent runs on either side on the next iteration.
- Rebuild the answer by expanding each surviving (character, count) entry into character * count.
Time-Based Key-Value Store
Constraints
- Timestamps passed to set for a given key are strictly increasing
- get returns the value for the largest timestamp <= the query timestamp
- get returns "" when the key is unknown or every stored timestamp is greater than the query
- 1 <= number of operations
Examples
Input: (["set", "get", "get", "set", "get", "get"], [["foo", "bar", 1], ["foo", 1], ["foo", 3], ["foo", "bar2", 4], ["foo", 4], ["foo", 5]])
Expected Output: [null, "bar", "bar", null, "bar2", "bar2"]
Explanation: get(foo,1)=bar (exact). get(foo,3)=bar (floor of 3 is timestamp 1). After set at 4: get(foo,4)=bar2 (exact). get(foo,5)=bar2 (floor of 5 is timestamp 4).
Input: (["set", "set", "get", "get", "get", "get", "get"], [["love", "high", 10], ["love", "low", 20], ["love", 5], ["love", 10], ["love", 15], ["love", 20], ["love", 25]])
Expected Output: [null, null, "", "high", "high", "low", "low"]
Explanation: get at 5 precedes the first timestamp (10) -> "". At 10 -> high. At 15 floors to 10 -> high. At 20 -> low. At 25 floors to 20 -> low.
Hints
- Because set timestamps are strictly increasing per key, each key's timestamp list stays sorted automatically — no sorting needed.
- A get is a floor query: find the rightmost stored timestamp that is <= the query timestamp.
- Use binary search (bisect_right then step back one index) instead of a linear scan to keep get at O(log m).
- If bisect_right returns 0, the query timestamp precedes every stored timestamp, so return the empty string.