Design and implement an LRU (Least Recently Used) cache. Your cache should support the following operations:
get(key)
- Gets the value (will always be positive) of the key if the key exists in the cache, otherwise returns -1.put(key, value)
- Sets or inserts the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least recently used item before inserting a new item. For the purpose of this question, when a new key is inserted, regard it as most recently used.The cache is initialized with a positive capacity.
Example:
LRUCache cache = new LRUCache(2); // capacity is 2
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // evicts key 1
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
Follow up:
Could you do both operations in O(1) time complexity?
Design and implement an LRU (Least Recently Used) cache. Your cache should support the following operations:
get(key)
- Gets the value (will always be positive) of the key if the key exists in the cache, otherwise returns -1.put(key, value)
- Sets or inserts the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least recently used item before inserting a new item. For the purpose of this question, when a new key is inserted, regard it as most recently used.The cache is initialized with a positive capacity.
Example:
LRUCache cache = new LRUCache(2); // capacity is 2
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // evicts key 1
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
Follow up:
Could you do both operations in O(1) time complexity?
A naive solution could use a simple array or list to store the key-value pairs along with a timestamp indicating the last time each key was accessed. The get
and put
operations would involve iterating through the array/list to find the key. When the cache is full, we would iterate through the array/list to find the least recently used item (smallest timestamp) and remove it.
This approach is easy to implement but has a time complexity of O(n) for both get
and put
operations, where n is the capacity of the cache, because you potentially have to scan the entire array to find the least recently used item.
To achieve O(1) time complexity for both get
and put
operations, we can use a combination of a doubly linked list and a hash map.
Here's how the get
and put
operations work:
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 = None
self.tail = None
self.size = 0
def get(self, key: int) -> int:
if key not in self.cache:
return -1
node = self.cache[key]
self._move_to_head(node)
return node.value
def put(self, key: int, value: int) -> None:
if key in self.cache:
node = self.cache[key]
node.value = value
self._move_to_head(node)
else:
new_node = Node(key, value)
self.cache[key] = new_node
self._add_to_head(new_node)
self.size += 1
if self.size > self.capacity:
self._remove_tail()
def _add_to_head(self, node):
if not self.head:
self.head = node
self.tail = node
else:
node.next = self.head
self.head.prev = node
self.head = node
def _remove_node(self, node):
if node.prev:
node.prev.next = node.next
else:
self.head = node.next
if node.next:
node.next.prev = node.prev
else:
self.tail = node.prev
def _move_to_head(self, node):
self._remove_node(node)
self._add_to_head(node)
def _remove_tail(self):
if self.tail:
del self.cache[self.tail.key]
self._remove_node(self.tail)
self.size -= 1
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
put
operation should not store any key-value pairs, and all get
operations should return -1.put
operation should update the value of the existing key and move the node to the head of the list, marking it as the most recently used item.get
operation should return -1.