LRU Cache medium
Problem Statement
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 sizecapacity
.int get(int key)
Return the value of thekey
if the key exists, otherwise return-1
.void put(int key, int value)
Update the value of thekey
if the key exists. Otherwise, add thekey-value
pair to the cache. If the number of keys exceeds thecapacity
from this operation, evict the least recently used key.
The functions get
and put
must each run in O(1)
average time complexity.
Example 1
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
Example 2
Input
["LRUCache", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [2, 1], [2], [3, 2], [2], [4, 4], [2], [3], [4]]
Output
[null, null, 1, null, -1, null, -4, -1, 4]
Steps and Explanation
The optimal solution uses a combination of a Dictionary
(or Hashtable
) for O(1) key lookup and a DoublyLinkedList
for O(1) insertion and deletion at both ends.
-
Data Structures:
Dictionary<int, Node> cache
: Stores key-value pairs along with a reference to aNode
in the doubly linked list.DoublyLinkedList list
: Maintains the order of elements based on LRU. The head represents the most recently used, and the tail represents the least recently used.
-
Node Class:
int key
: Key of the cache entry.int value
: Value of the cache entry.Node prev
: Pointer to the previous node.Node next
: Pointer to the next node.
-
get(key)
:- Check if the key exists in the
cache
. - If found:
- Move the corresponding node to the head of the
list
(making it the most recently used). - Return the value.
- Move the corresponding node to the head of the
- If not found: Return -1.
- Check if the key exists in the
-
put(key, value)
:- Check if the key exists in the
cache
. - If found:
- Update the value.
- Move the corresponding node to the head of the
list
.
- If not found:
- Create a new
Node
. - Add the new
Node
to the head of thelist
. - Add the key-value pair to the
cache
. - If the cache size exceeds
capacity
:- Remove the tail node (LRU) from the
list
. - Remove the corresponding key from the
cache
.
- Remove the tail node (LRU) from the
- Create a new
- Check if the key exists in the
Code
using System.Collections.Generic;
public class Node
{
public int key;
public int value;
public Node prev;
public Node next;
public Node(int k, int v) { key = k; value = v; }
}
public class DoublyLinkedList
{
public Node head;
public Node tail;
public int count;
public DoublyLinkedList() { head = new Node(0, 0); tail = new Node(0, 0); head.next = tail; tail.prev = head; count = 0; }
public void addFirst(Node node)
{
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
count++;
}
public void remove(Node node)
{
node.prev.next = node.next;
node.next.prev = node.prev;
count--;
}
public Node removeLast()
{
if (count == 0) return null;
Node last = tail.prev;
remove(last);
return last;
}
}
public class LRUCache {
private Dictionary<int, Node> cache;
private DoublyLinkedList list;
private int capacity;
public LRUCache(int capacity) {
this.capacity = capacity;
cache = new Dictionary<int, Node>();
list = new DoublyLinkedList();
}
public int Get(int key) {
if (!cache.ContainsKey(key)) return -1;
Node node = cache[key];
list.remove(node);
list.addFirst(node);
return node.value;
}
public void Put(int key, int value) {
if (cache.ContainsKey(key))
{
list.remove(cache[key]);
cache[key].value = value;
list.addFirst(cache[key]);
}
else
{
Node node = new Node(key, value);
cache.Add(key, node);
list.addFirst(node);
if (list.count > capacity)
{
Node last = list.removeLast();
cache.Remove(last.key);
}
}
}
}
Complexity
-
Time Complexity:
get(key)
: O(1) on average.put(key, value)
: O(1) on average.
-
Space Complexity: O(capacity) to store the cache entries.