Quick Overview

Implement a fixed-capacity cache that evicts the least recently used key, processing a sequence of get and put operations and returning the result of every get. Tests recency bookkeeping, update versus insert semantics, eviction order and constant-time operations.

Simulate a fixed-capacity least-recently-used cache over get and put operations

Company: ByteDance

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: hard

Interview Round: Technical Screen

Design a cache with a fixed capacity that evicts the least recently used key when it is full. To make it testable as one function, you receive the capacity and a sequence of operations, and you return the results of the `get` operations in order. ### Function Signature ```python def run_lru_cache(capacity: int, operations: list[str], arguments: list[list[int]]) -> list[int]: ``` `operations[i]` is `"get"` or `"put"`, and `arguments[i]` holds its arguments: `[key]` for `"get"` and `[key, value]` for `"put"`. ### Rules - The cache starts empty and holds at most `capacity` keys. - `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 nothing changes. - `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 removed first, and then `key` is inserted as the most recently used key. - A key is "used" when it is inserted or updated by `put`, or read by a successful `get`. - Return one integer per `get`, in the order the `get` operations appear. `put` produces no output. ### Constraints - `1 <= capacity <= 3000` - `1 <= len(operations) == len(arguments) <= 2 * 10^5` - `0 <= key <= 10^4` and `0 <= value <= 10^9` - Aim for constant average time per operation. ### Examples **Example 1** ```text Input: capacity = 2, operations = ["put", "put", "get", "put", "get", "put", "get", "get", "get"], arguments = [[1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]] Output: [1, -1, -1, 3, 4] ``` After the two puts, key `1` is the least recently used. `get(1)` returns `1` and makes key `2` the least recently used, so `put(3, 3)` evicts `2` and `get(2)` returns `-1`. `put(4, 4)` then evicts `1`, so `get(1)` returns `-1`, while `get(3)` and `get(4)` return `3` and `4`. **Example 2** ```text Input: capacity = 2, operations = ["put", "put", "put", "put", "get", "get"], arguments = [[1, 10], [2, 20], [1, 15], [3, 30], [1], [2]] Output: [15, -1] ``` `put(1, 15)` updates an existing key without eviction and makes `1` the most recently used, so `put(3, 30)` evicts `2`. **Example 3** ```text Input: capacity = 1, operations = ["get", "put", "get", "put", "get", "get"], arguments = [[5], [5, 0], [5], [6, 7], [5], [6]] Output: [-1, 0, -1, 7] ``` A stored value of `0` is a hit, not a miss. With capacity `1`, `put(6, 7)` evicts key `5`.

Overview: Implement a fixed-capacity cache that evicts the least recently used key, processing a sequence of get and put operations and returning the result of every get. Tests recency bookkeeping, update versus insert semantics, eviction order and constant-time operations.

Design a cache with a fixed capacity that evicts the least recently used key when it is full. To make it testable as one function, you receive the capacity and a sequence of operations, and you return the results of the `get` operations in order. Implement `run_lru_cache(capacity, operations, arguments)`. `operations[i]` is `"get"` or `"put"`, and `arguments[i]` holds its arguments: `[key]` for `"get"` and `[key, value]` for `"put"`. ### Rules - The cache starts empty and holds at most `capacity` keys. - `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 nothing changes. - `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 removed first, and then `key` is inserted as the most recently used key. - A key is "used" when it is inserted or updated by `put`, or read by a successful `get`. - Return one integer per `get`, in the order the `get` operations appear. `put` produces no output, so a sequence with no `get` operations returns an empty list. ### Constraints - `1 <= capacity <= 3000` - `1 <= len(operations) == len(arguments) <= 2 * 10^5` - `0 <= key <= 10^4` and `0 <= value <= 10^9` - No input or output value exceeds 2^31 - 1, so a 32-bit `int` is sufficient in Java and C++. - Aim for constant average time per operation. ### Example 1 ```text Input: capacity = 2, operations = ["put", "put", "get", "put", "get", "put", "get", "get", "get"], arguments = [[1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]] Output: [1, -1, -1, 3, 4] ``` After the two puts, key `1` is the least recently used. `get(1)` returns `1` and makes key `2` the least recently used, so `put(3, 3)` evicts `2` and `get(2)` returns `-1`. `put(4, 4)` then evicts `1`, so `get(1)` returns `-1`, while `get(3)` and `get(4)` return `3` and `4`. ### Example 2 ```text Input: capacity = 1, operations = ["get", "put", "get", "put", "get", "get"], arguments = [[5], [5, 0], [5], [6, 7], [5], [6]] Output: [-1, 0, -1, 7] ``` A stored value of `0` is a hit, not a miss. With capacity `1`, `put(6, 7)` evicts key `5`.

Constraints

  • 1 <= capacity <= 3000
  • 1 <= len(operations) == len(arguments) <= 2 * 10^5
  • operations[i] is "get" or "put"; arguments[i] is [key] for "get" and [key, value] for "put"
  • 0 <= key <= 10^4
  • 0 <= value <= 10^9
  • No input or output value exceeds 2^31 - 1 (32-bit int suffices in Java and C++)
  • Aim for constant average time per operation.

Examples

Input: (2, ['put', 'put', 'get', 'put', 'get', 'put', 'get', 'get', 'get'], [[1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]])

Expected Output: [1, -1, -1, 3, 4]

Explanation: Source Example 1: the successful get(1) refreshes key 1, so put(3, 3) evicts 2 and put(4, 4) then evicts 1.

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

Expected Output: [15, -1]

Explanation: Source Example 2: updating the LRU key 1 replaces its value and makes it most recently used without eviction, so put(3, 30) evicts 2.

Hints

  1. Trace Example 1 by hand, listing the cached keys from least to most recently used after every operation, and note exactly which operations reorder that list.
  2. A missed get changes nothing, and updating a key that is already cached never evicts anything, even when the cache is full.
  3. Since 0 is a valid stored value, decide whether a key is cached by membership, not by the value it maps to.

Loading coding console...

Show the approach

Approach

Keys are bounded to 0..10^4, so every key owns a fixed slot in four arrays (previous link, next link, value, present flag), and two extra slots (10001 and 10002) act as the head and tail sentinels of a doubly linked recency list. Invariant: after every operation the list holds exactly the cached keys, ordered from most recently used (next to head) to least recently used (next to tail), and count equals the list length, which never exceeds capacity. get(key): if the key is present, unlink its slot, relink it right after head (it is now the most recently used) and output its value; otherwise output -1 and touch nothing, so a miss changes neither contents nor order. put(key, value): if the key is present, overwrite its value and move it to the front without evicting and without changing count; otherwise, if count already equals capacity, unlink the slot just before tail (the least recently used key) and clear its present flag, then store the value, mark the key present and link it at the front. Each rule of the statement maps to a constant number of pointer updates that preserve the invariant, so every get reports exactly the value the rules prescribe, in operation order. Edge cases: capacity 1 (every new key evicts the only cached key), a stored value of 0 (a hit, decided by the present flag rather than the value), updates on a full cache (no eviction), gets of evicted keys (-1), and sequences with no get (empty list). Here n = len(operations) and K = 10^4 + 3 array slots.

Time complexity:
O(n + K)
Space complexity:
O(n + K)