Design a data structure that follows the constraints of a Least Recently Used (LRU) cache. Implement the LRUCache
class:
LRUCache(int capacity)
Initialize the LRU cache with positive size capacity
.int get(int key)
Return the value of the key
if the key exists, otherwise return -1
.void put(int key, int value)
Update the value of the key
if the key
exists. Otherwise, add the key-value
pair to the cache. If the number of keys exceeds the capacity
from this operation, evict the least recently used key.The functions get
and put
must each run in O(1)
average time complexity.
Example:
Input
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
Output
[null, null, null, 1, null, -1, null, -1, 3, 4]
Explanation
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // cache is {1=1}
lRUCache.put(2, 2); // cache is {1=1, 2=2}
lRUCache.get(1); // return 1
lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}
lRUCache.get(2); // returns -1 (not found)
lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}
lRUCache.get(1); // return -1 (not found)
lRUCache.get(3); // return 3
lRUCache.get(4); // return 4
Design a data structure that implements a Least Recently Used (LRU) cache.
The LRUCache
class should have:
LRUCache(int capacity)
: Initializes the cache with a positive capacity.int get(int key)
: Returns the value associated with the key. Return -1 if the key does not exist.void put(int key, int value)
: Updates the value if the key exists. Otherwise, insert the key-value pair. If inserting causes the cache to exceed the capacity, evict the least recently used key.The get
and put
operations must run in O(1) time on average.
A simple approach would be to use a dictionary (hash map) to store the key-value pairs and an array or list to keep track of the order in which keys are accessed. When a get
or put
operation is performed, the list is updated to reflect the most recently used item. When the capacity is exceeded, the least recently used item (located at the start of the list) is removed.
get
- O(n) to search for the key in the list and update the order. put
- O(n) in the worst case if shifting elements after eviction. O(1) for dictionary access.This approach does not meet the O(1) average time complexity requirement.
The optimal approach utilizes a combination of a hash map and a doubly linked list.
get
and put
operations.Algorithm:
get(key)
:
put(key, value)
:
Edge Cases:
put
operation should update the value if the key already exists.get
operation should return -1 when the cache is empty.Code Example (Python):
class Node:
def __init__(self, key, value):
self.key = key
self.value = value
self.next = None
self.prev = None
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.cache = {}
self.head = Node(0, 0) # Dummy head
self.tail = Node(0, 0) # Dummy tail
self.head.next = self.tail
self.tail.prev = self.head
def get(self, key: int) -> int:
if key in self.cache:
node = self.cache[key]
self._move_to_tail(node)
return node.value
else:
return -1
def put(self, key: int, value: int) -> None:
if key in self.cache:
node = self.cache[key]
node.value = value
self._move_to_tail(node)
else:
node = Node(key, value)
self.cache[key] = node
self._add_to_tail(node)
if len(self.cache) > self.capacity:
self._remove_lru()
def _move_to_tail(self, node):
self._remove_node(node)
self._add_to_tail(node)
def _add_to_tail(self, node):
node.prev = self.tail.prev
node.next = self.tail
self.tail.prev.next = node
self.tail.prev = node
def _remove_node(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def _remove_lru(self):
lru_node = self.head.next
del self.cache[lru_node.key]
self._remove_node(lru_node)
get(key)
: O(1) - Hash map lookup and linked list move.put(key, value)
: O(1) - Hash map lookup/insertion and linked list add/remove.