Quick Overview

Simulate a fixed-capacity key-value cache that evicts the least recently used key: process a sequence of get and put operations on an empty cache and return the result of every get. Tests exact recency rules for hits, misses, updates and evictions, with constant-time operations over up to 200,000 calls.

Simulate a Fixed-Capacity Cache With Least-Recently-Used Eviction

Company: Bnsf

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Technical Screen

A cache holds at most `capacity` key-value pairs. When a new key must be inserted into a full cache, the cache first evicts the key that was used least recently. Process a sequence of `get` and `put` operations on an initially empty cache and return the result of every `get`, in order. ### Function Signature ```python def lru_get_results(capacity: int, operations: list[list[int]]) -> list[int]: ``` ### Rules - Each operation is a list of integers. A list of length 1, `[key]`, is `get(key)`; a list of length 2, `[key, value]`, is `put(key, value)`. - `get(key)`: if `key` is in the cache, the result is its value and `key` becomes the most recently used key. Otherwise the result is `-1` and the cache does not change. - `put(key, value)`: if `key` is already in the cache, its value is replaced and it becomes the most recently used key; nothing is evicted. Otherwise, if the cache already holds `capacity` keys, the least recently used key is evicted first; then `key` is inserted with `value` as the most recently used key. - A key's recency changes only when a `get` finds it or a `put` writes it. A `get` that misses changes nothing. - Return a list with one integer per `get` operation, in the order the operations appear. `put` operations add nothing to it. If there is no `get`, return an empty list. - Each `get` and `put` should run in $O(1)$ average time. ### Constraints - `1 <= capacity <= 10^5` - `1 <= len(operations) <= 2 * 10^5` - `0 <= key <= 10^9` - `0 <= value <= 10^9`, so a result of `-1` always means the key was not found. - All values fit in a signed 32-bit integer. ### Examples **Example 1** ```text Input: capacity = 2 operations = [[5, 50], [7, 70], [5], [9, 90], [7], [5], [9]] Output: [50, -1, 50, 90] ``` After the two puts, the cache holds 5 and 7, with 5 the least recently used. `get(5)` returns 50 and makes 7 the least recently used, so `put(9, 90)` evicts 7. `get(7)` then returns -1, `get(5)` returns 50 and `get(9)` returns 90. **Example 2** ```text Input: capacity = 2 operations = [[1, 10], [2, 20], [1, 15], [3, 30], [1], [2], [3]] Output: [15, -1, 30] ``` `put(1, 15)` updates an existing key without evicting anything and makes 1 the most recently used, so `put(3, 30)` evicts 2. The gets return 15, -1 and 30. **Example 3** ```text Input: capacity = 1 operations = [[4], [4, 0], [4], [6, 1], [4], [6]] Output: [-1, 0, -1, 1] ``` The first `get(4)` misses on the empty cache. After `put(4, 0)`, `get(4)` returns the stored value 0. `put(6, 1)` evicts 4 from the one-slot cache, so `get(4)` misses and `get(6)` returns 1.

Overview: Simulate a fixed-capacity key-value cache that evicts the least recently used key: process a sequence of get and put operations on an empty cache and return the result of every get. Tests exact recency rules for hits, misses, updates and evictions, with constant-time operations over up to 200,000 calls.

Read the full Bnsf Software Engineer interview experience this question came from

A cache holds at most `capacity` key-value pairs. When a new key must be inserted into a full cache, the cache first evicts the key that was used least recently. Starting from an empty cache, process a sequence of `get` and `put` operations and return the result of every `get`, in order. Implement `lru_get_results(capacity, operations)`. ### Rules - Each operation is a list of integers. A list of length 1, `[key]`, is `get(key)`; a list of length 2, `[key, value]`, is `put(key, value)`. - `get(key)`: if `key` is in the cache, the result is its value and `key` becomes the most recently used key. Otherwise the result is `-1` and the cache does not change. - `put(key, value)`: if `key` is already in the cache, its value is replaced and it becomes the most recently used key; nothing is evicted. Otherwise, if the cache already holds `capacity` keys, the least recently used key is evicted first; then `key` is inserted with `value` as the most recently used key. - A key's recency changes only when a `get` finds it or a `put` writes it. A `get` that misses changes nothing. - Return a list with one integer per `get` operation, in the order the operations appear. `put` operations add nothing to it. If there is no `get`, return an empty list. - Each `get` and `put` should run in O(1) average time. ### Constraints - `1 <= capacity <= 10^5` - `1 <= len(operations) <= 2 * 10^5` - Every operation has length 1 (`get`) or 2 (`put`). - `0 <= key <= 10^9` - `0 <= value <= 10^9`, so a result of `-1` always means the key was not found. - All values fit in a signed 32-bit integer (none exceeds 2^31 - 1), so a 32-bit `int` is enough in every language. ### Example 1 ```text Input: capacity = 2 operations = [[5, 50], [7, 70], [5], [9, 90], [7], [5], [9]] Output: [50, -1, 50, 90] ``` After the two puts, the cache holds 5 and 7, with 5 the least recently used. `get(5)` returns 50 and makes 7 the least recently used, so `put(9, 90)` evicts 7. `get(7)` then returns -1, `get(5)` returns 50 and `get(9)` returns 90. ### Example 2 ```text Input: capacity = 2 operations = [[1, 10], [2, 20], [1, 15], [3, 30], [1], [2], [3]] Output: [15, -1, 30] ``` `put(1, 15)` updates an existing key without evicting anything and makes 1 the most recently used, so `put(3, 30)` evicts 2. The gets return 15, -1 and 30.

Constraints

  • 1 <= capacity <= 10^5
  • 1 <= len(operations) <= 2 * 10^5
  • Every operation has length 1 ([key] = get) or 2 ([key, value] = put)
  • 0 <= key <= 10^9
  • 0 <= value <= 10^9, so a result of -1 always means the key was not found
  • All values fit in a signed 32-bit integer (none exceeds 2^31 - 1)

Examples

Input: (2, [[5, 50], [7, 70], [5], [9, 90], [7], [5], [9]])

Expected Output: [50, -1, 50, 90]

Explanation: Source Example 1: the get(5) hit makes 7 least recently used, so put(9, 90) evicts 7.

Input: (2, [[1, 10], [2, 20], [1, 15], [3, 30], [1], [2], [3]])

Expected Output: [15, -1, 30]

Explanation: Source Example 2: put(1, 15) updates key 1 in place without evicting and refreshes it, so put(3, 30) evicts 2.

Hints

  1. Only two events change a key's recency: a get that finds it and a put that writes it. A get that misses must leave the cache exactly as it was.
  2. A put to a key that is already cached replaces its value and makes it the most recently used, but never evicts anything, even when the cache is full.
  3. For O(1) average time you need to find a key's entry, move it to the most-recently-used end, and remove the least-recently-used entry, each without scanning the cache.

Loading coding console...

Show the approach

Approach

Keep two structures together: a hash map from each cached key to its entry, and a doubly linked list of the entries ordered from least to most recently used (Python's OrderedDict provides both; the other references use index arrays for the list, with node 0 as a circular sentinel, and reuse the evicted node's slot). For get(key), look the key up: on a miss append -1 and change nothing; on a hit move the entry to the most-recent end and append its value. For put(key, value), if the key is cached, overwrite its value and move it to the most-recent end without evicting; otherwise, if the cache already holds capacity keys, unlink the entry at the least-recent end and delete its map entry, then append the new entry at the most-recent end. Invariant: after every operation the list holds exactly the cached keys, ordered by their last successful get or put, oldest first, so the head is always the key the rules say to evict. Each operation is one hash lookup plus a constant number of link changes, so it runs in O(1) average time. Edge cases: a get on an empty cache returns -1; a stored value of 0 is returned as 0, distinct from the -1 miss; with capacity 1 every new key evicts the previous one; updating an existing key never evicts, even when the cache is full; a key that was evicted and is put again becomes a brand-new most-recent entry; with no get operations the result is an empty list.

Time complexity:
O(n) for n = len(operations), O(1) average per get or put
Space complexity:
O(min(capacity, n)) for the cache, plus O(n) for the result list