Question 146
Question 146
LRU Cache
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get(key)
and put(key, value)
.
get(key)
- Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value)
- Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
The cache is initialized with a positive capacity.
Example:
LRUCache cache = new LRUCache( 2 /* capacity */ );
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
Note:
get
andput
operations take O(1) time complexity.- The cache is not thread-safe.
- Do not use any built-in libraries such as
HashMap
orLinkedHashMap
. - Do not use any external libraries such as
Guava
.
Solution
To implement an LRU cache, we need to keep track of the most recently used items and the least recently used items. We can use a doubly linked list to implement this data structure.
We can define a Node
class to represent each item in the cache. Each Node
has a key
, value
, and pointers to the previous and next nodes in the list.
We can also define a LRUCache
class to represent the cache. The cache has a capacity, a HashMap
to store the key-value pairs, and a doubly linked list to keep track of the most recently used and least recently used items. The put
method first checks if the key is already in the cache. If it is, we update the value and move the node to the front of the list. If it is not, we create a new node and insert it at the front of the list. If the cache is full, we remove the least recently used item from the list and remove it from the HashMap
.
The get
method first checks if the key is in the cache. If it is, we move the node to the front of the list and return the value. If it is not, we return -1.
Here is the implementation of the LRUCache
class:
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
}
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}