题目
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(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.
解答
LRU Cache,是一种经典的Cache结构。考虑这几点:
- 第一点:确定存储Cache元素的数据结构。Cache一般是顺序结构,在Cache的所有元素中,最近访问的应该放在前面,最久没访问的应该放在靠后的位置。如果用数组来组织,主要问题是移动cache中的某个元素位置(如插到头部或从尾部删除)时,不可避免地要改变部分或全部元素的位置,代价太大。如果用单链表来组织,移动元素位置只需要改变链表节点的next指针。但在把最近访问的元素插到头部,或者从尾部删除最久没有访问的元素时,需要从前往后遍历链表,来确定被操作节点的前驱节点的位置,详细解释可参考LRU cache implementation - Is a double linked list necessary ? 。因此,最终确定用来存放cache数据的结构是双向链表,它很好地解决了上面提到的两个问题。
- 第二点:设计查找Cache元素的数据结构。众所周知,访问链表只能从前往后依次遍历。这种方式可行,但不高效。而题目中给出了元素的key和value,所以很容易联想到使用哈希表。其中,哈希表的键是元素的key,值是元素在链表中节点的地址指针。
综上所述,实现一个LRU Cache需要用到双向链表(doubly linked list)和哈希表(hash map)这两种数据结构。下面是参考如何用C++实现一个LRU Cache这篇文章完成的AC代码:
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
// define doubly linked list node
typedef struct Node {
int key;
int val;
Node *previous;
Node *next;
}Node;
class LRUCache{
public:
LRUCache(int capacity) {
linkedList = (Node *)malloc(sizeof(Node) * capacity);
for (int i = 0; i < capacity; i++) {
freeEntries.push_back(linkedList + i);
}
head = (Node *)malloc(sizeof(Node));
tail = (Node *)malloc(sizeof(Node));
head->previous = NULL;
head->next = tail;
tail->previous = head;
tail->next = NULL;
}
int get(int key) {
// first find the node of specified key
Node *p = hash[key];
// if found, move the node into head, then return its value
if (p) {
// deattach the node
p->previous->next = p->next;
p->next->previous = p->previous;
// insert in new position
p->next = head->next;
head->next->previous = p;
p->previous = head;
head->next = p;
return p->val;
}
else { // not found
return -1;
}
}
void set(int key, int value) {
Node *p = hash[key]; // if not found, p is ponited NULL(0x0)
// if found, modify value, then move it to head
if (p) {
// deattach the node
p->previous->next = p->next;
p->next->previous = p->previous;
// modify the value
p->val = value;
// insert in new position
p->next = head->next;
head->next->previous = p;
p->previous = head;
head->next = p;
}
else { // if not found, fetch a free space, then insert it into head
if (freeEntries.empty()) { // cache is full
p = tail->previous;
// deattach the node
p->previous->next = p->next;
p->next->previous = p->previous;
hash.erase(p->key);
}
else { // cache is not full
p = freeEntries.back(); // get the last free element from free list
freeEntries.pop_back(); // delete the last element
}
p->key = key;
p->val = value;
hash[key] = p;
// insert the new node into head
p->next = head->next;
head->next->previous = p;
p->previous = head;
head->next = p;
}
}
private:
Node *head;
Node *tail;
Node *linkedList;
unordered_map<int, Node*> hash; // look up key faster
vector<Node * > freeEntries; // store available nodes
};